From 39e1f35c18149a0a3f10dd8ae21fc3384eac3057 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20BRINDLE?= <sebastien.brindle@kleegroup.com> Date: Fri, 24 Jan 2025 16:10:34 +0100 Subject: [PATCH 01/45] Manage complex form styling. --- css/drupal.form.css | 15 +++++++ css/drupal.tabledrag.css | 29 ++++++++++++ css/drupal.vertical-tabs.css | 12 +++++ dsfr4drupal.info.yml | 6 +++ dsfr4drupal.libraries.yml | 17 +++++++ js/drupal.tabledrag.js | 78 +++++++++++++++++++++++++++++++++ templates/layout/page.html.twig | 2 +- 7 files changed, 158 insertions(+), 1 deletion(-) create mode 100644 css/drupal.form.css create mode 100644 css/drupal.tabledrag.css create mode 100644 css/drupal.vertical-tabs.css create mode 100644 js/drupal.tabledrag.js diff --git a/css/drupal.form.css b/css/drupal.form.css new file mode 100644 index 0000000..d9c96a3 --- /dev/null +++ b/css/drupal.form.css @@ -0,0 +1,15 @@ + + +:root { + --form-spacing: 1.5rem; + --form-item-spacing: var(--form-spacing); +} + +main[role="main"] > .fr-container > form, +main[role="main"] > .fr-container--fluid > form { + margin-block: var(--form-spacing); +} + +.form-item { + margin-block: var(--form-item-spacing); +} diff --git a/css/drupal.tabledrag.css b/css/drupal.tabledrag.css new file mode 100644 index 0000000..1d1e775 --- /dev/null +++ b/css/drupal.tabledrag.css @@ -0,0 +1,29 @@ +/** + * @file + * Manage tabledrag styling. + */ + +a.tabledrag-handle[href] { + background-image: none; +} + +.draggable a.tabledrag-handle { + margin: 0; +} + +a.tabledrag-handle .handle { + margin: 0; + padding: 0; + min-width: 16px; + min-height: 16px; + height: 100%; + background-position: center center; +} + +.field-multiple-drag { + max-width: fit-content; +} + +.tabledrag-toggle-weight { + font-size: .75em; +} diff --git a/css/drupal.vertical-tabs.css b/css/drupal.vertical-tabs.css new file mode 100644 index 0000000..1d03a4e --- /dev/null +++ b/css/drupal.vertical-tabs.css @@ -0,0 +1,12 @@ +/** + * @file + * Manage vertical tabs styling. + */ + +.vertical-tabs__menu [href] { + background-image: none; +} + +.vertical-tabs__pane { + padding: 1.5rem; +} diff --git a/dsfr4drupal.info.yml b/dsfr4drupal.info.yml index 17068cb..237e4af 100644 --- a/dsfr4drupal.info.yml +++ b/dsfr4drupal.info.yml @@ -36,8 +36,14 @@ libraries: - dsfr4drupal/utility libraries-extend: + core/drupal.form: + - dsfr4drupal/drupal.form core/drupal.message: - dsfr4drupal/drupal.message + core/drupal.tabledrag: + - dsfr4drupal/drupal.tabledrag + core/drupal.vertical-tabs: + - dsfr4drupal/drupal.vertical-tabs navigation/navigation.layout: - dsfr4drupal/navigation.layout node/drupal.node.preview: diff --git a/dsfr4drupal.libraries.yml b/dsfr4drupal.libraries.yml index 3ccc4c0..0857cec 100644 --- a/dsfr4drupal.libraries.yml +++ b/dsfr4drupal.libraries.yml @@ -545,6 +545,11 @@ core: production: true verbose: true +drupal.form: + css: + theme: + css/drupal.form.css: {} + drupal.message: js: js/messages.js: {} @@ -556,6 +561,18 @@ drupal.node.preview: theme: css/drupal.node.preview.css: {} +drupal.tabledrag: + css: + theme: + css/drupal.tabledrag.css: {} + js: + js/drupal.tabledrag.js: {} + +drupal.vertical-tabs: + css: + theme: + css/drupal.vertical-tabs.css: {} + #legacy: # js: # /libraries/dsfr/dist/legacy/legacy.nomodule.min.js: diff --git a/js/drupal.tabledrag.js b/js/drupal.tabledrag.js new file mode 100644 index 0000000..f363909 --- /dev/null +++ b/js/drupal.tabledrag.js @@ -0,0 +1,78 @@ +/** + * @file + * Provide dragging capabilities to admin uis. + */ + +/** + * Triggers when weights columns are toggled. + * + * @event columnschange + */ + +(function ($, Drupal, drupalSettings) { + /** + * Store the state of weight columns display for all tables. + * + * Default value is to hide weight columns. + */ + let showWeight = JSON.parse( + localStorage.getItem("Drupal.tableDrag.showWeight"), + ); + + // React to localStorage event showing or hiding weight columns. + $(window).on( + "storage", + function (e) { + // Only react to "Drupal.tableDrag.showWeight" value change. + if (e.originalEvent.key === "Drupal.tableDrag.showWeight") { + // This was changed in another window, get the new value for this + // window. + showWeight = JSON.parse(e.originalEvent.newValue); + this.displayColumns(showWeight); + } + }.bind(this), + ); + + /** + * Hide the columns containing weight/parent form elements. + * + * Undo showColumns(). + */ + Drupal.tableDrag.prototype.hideColumns = function () { + const $tables = $(once.filter('tabledrag', 'table')); + // Hide weight/parent cells and headers. + $tables.find('.tabledrag-hide').each(function () { + this.style.display = 'none'; + }); + // Show TableDrag handles. + $tables.find('.tabledrag-handle').each(function () { + this.style.display = ''; + }); + // Reduce the colspan of any effected multi-span columns. + $tables.find('.tabledrag-has-colspan').each(function () { + this.colSpan -= 1; + }); + }; + + /** + * Show the columns containing weight/parent form elements. + * + * Undo hideColumns(). + */ + Drupal.tableDrag.prototype.showColumns = function () { + const $tables = $(once.filter('tabledrag', 'table')); + // Show weight/parent cells and headers. + $tables.find('.tabledrag-hide').each(function () { + this.style.display = ''; + }); + // Hide TableDrag handles. + $tables.find('.tabledrag-handle').each(function () { + this.style.display = 'none'; + }); + // Increase the colspan for any columns where it was previously reduced. + $tables.find('.tabledrag-has-colspan').each(function () { + this.colSpan += 1; + }); + }; + +})(jQuery, Drupal, drupalSettings); diff --git a/templates/layout/page.html.twig b/templates/layout/page.html.twig index 3d79ad9..0433eea 100644 --- a/templates/layout/page.html.twig +++ b/templates/layout/page.html.twig @@ -11,7 +11,7 @@ <div class="{{ container_class|default('fr-container') }}"> {{ page.breadcrumb }} </div> -<main id="main" role="main" class="{{ container_class|default('fr-container') }}" tabindex="-1"> +<main id="main" role="main" tabindex="-1"> <div class="{{ container_class|default('fr-container') }}"> {{ page.content }} </div> -- GitLab From 3518edfdd9febbf2c23dee63b6ef40d8e2480518 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20BRINDLE?= <sebastien.brindle@kleegroup.com> Date: Sat, 25 Jan 2025 12:06:08 +0100 Subject: [PATCH 02/45] Manage tabledrag toggle weight button styling. --- css/drupal.form.css | 4 +- css/drupal.tabledrag.css | 4 + js/drupal.tabledrag.js | 90 +++++++++---------- .../form/field-multiple-value-form.html.twig | 2 +- 4 files changed, 48 insertions(+), 52 deletions(-) diff --git a/css/drupal.form.css b/css/drupal.form.css index d9c96a3..59f76c9 100644 --- a/css/drupal.form.css +++ b/css/drupal.form.css @@ -1,4 +1,6 @@ - +/** + * Mange from styling with DSFR. + */ :root { --form-spacing: 1.5rem; diff --git a/css/drupal.tabledrag.css b/css/drupal.tabledrag.css index 1d1e775..55733fc 100644 --- a/css/drupal.tabledrag.css +++ b/css/drupal.tabledrag.css @@ -27,3 +27,7 @@ a.tabledrag-handle .handle { .tabledrag-toggle-weight { font-size: .75em; } + +.tabledrag-toggle-weight-wrapper + .fr-table { + margin-top: 0; +} diff --git a/js/drupal.tabledrag.js b/js/drupal.tabledrag.js index f363909..8d38816 100644 --- a/js/drupal.tabledrag.js +++ b/js/drupal.tabledrag.js @@ -19,60 +19,50 @@ localStorage.getItem("Drupal.tableDrag.showWeight"), ); - // React to localStorage event showing or hiding weight columns. - $(window).on( - "storage", - function (e) { - // Only react to "Drupal.tableDrag.showWeight" value change. - if (e.originalEvent.key === "Drupal.tableDrag.showWeight") { - // This was changed in another window, get the new value for this - // window. - showWeight = JSON.parse(e.originalEvent.newValue); - this.displayColumns(showWeight); - } - }.bind(this), - ); + const initColumnsOriginal = Drupal.tableDrag.prototype.initColumns; /** - * Hide the columns containing weight/parent form elements. - * - * Undo showColumns(). + * @inheritDoc */ - Drupal.tableDrag.prototype.hideColumns = function () { - const $tables = $(once.filter('tabledrag', 'table')); - // Hide weight/parent cells and headers. - $tables.find('.tabledrag-hide').each(function () { - this.style.display = 'none'; - }); - // Show TableDrag handles. - $tables.find('.tabledrag-handle').each(function () { - this.style.display = ''; - }); - // Reduce the colspan of any effected multi-span columns. - $tables.find('.tabledrag-has-colspan').each(function () { - this.colSpan -= 1; - }); - }; + Drupal.tableDrag.prototype.initColumns = function () { + const $tableWrapper = this.$table.parents(".fr-table"); + const $toggleWeightWrapper = this.$table.prev(); - /** - * Show the columns containing weight/parent form elements. - * - * Undo hideColumns(). - */ - Drupal.tableDrag.prototype.showColumns = function () { - const $tables = $(once.filter('tabledrag', 'table')); - // Show weight/parent cells and headers. - $tables.find('.tabledrag-hide').each(function () { - this.style.display = ''; - }); - // Hide TableDrag handles. - $tables.find('.tabledrag-handle').each(function () { - this.style.display = 'none'; - }); - // Increase the colspan for any columns where it was previously reduced. - $tables.find('.tabledrag-has-colspan').each(function () { - this.colSpan += 1; - }); + // Move toggle weight wrapper before DSFR table wrapper. + $tableWrapper.before($toggleWeightWrapper); + + // Call original "initColumns" method. + initColumnsOriginal.call(this); }; + $.extend( + Drupal.theme, + /** @lends Drupal.theme */ { + /** + * Constructs contents of the toggle weight button. + * + * @param {boolean} show + * If the table weights are currently displayed. + * + * @return {string} + * HTML markup for the weight toggle button content. + */ + toggleButtonContent: (show) => { + const classes = [ + 'tabledrag-toggle-weight', + 'fr-icon--sm', + ]; + let text = ''; + if (show) { + classes.push('fr-icon-eye-off-line'); + text = Drupal.t('Hide row weights'); + } else { + classes.push('fr-icon-eye-line'); + text = Drupal.t('Show row weights'); + } + return `<span class="${classes.join(' ')}" aria-hidden="true"></span> ${text}`; + }, + }, + ); + })(jQuery, Drupal, drupalSettings); diff --git a/templates/form/field-multiple-value-form.html.twig b/templates/form/field-multiple-value-form.html.twig index 46964d3..6654c13 100644 --- a/templates/form/field-multiple-value-form.html.twig +++ b/templates/form/field-multiple-value-form.html.twig @@ -1,7 +1,7 @@ {% if multiple %} {% set classes = [ 'js-form-item', - 'form-item' + 'form-item', ] %} <div{{ attributes.addClass(classes) }}> -- GitLab From efa84aae5202d821053e1735f74c81b0ab5b40e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20BRINDLE?= <sebastien.brindle@kleegroup.com> Date: Sat, 25 Jan 2025 12:09:04 +0100 Subject: [PATCH 03/45] Fix input width into vertical tabs pane. --- css/drupal.vertical-tabs.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/css/drupal.vertical-tabs.css b/css/drupal.vertical-tabs.css index 1d03a4e..be39efc 100644 --- a/css/drupal.vertical-tabs.css +++ b/css/drupal.vertical-tabs.css @@ -10,3 +10,7 @@ .vertical-tabs__pane { padding: 1.5rem; } + +.vertical-tabs__pane .fr-input { + box-sizing: border-box; +} -- GitLab From 5507a9bfaa66bd685625feae7509c0dc643f32c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20BRINDLE?= <sebastien.brindle@kleegroup.com> Date: Sat, 25 Jan 2025 19:01:18 +0100 Subject: [PATCH 04/45] Manage media library styles. --- components/card/card.component.yml | 3 ++ components/card/card.twig | 3 +- css/component/checkbox.css | 18 +++++++ css/component/form.css | 44 +++++++++++++++++ .../node-preview.css} | 0 .../tabledrag.css} | 0 css/{ => component}/toolbar.css | 0 .../vertical-tabs.css} | 0 css/component/views-exposed-form.css | 41 ++++++++++++++++ css/drupal.form.css | 17 ------- css/{ => theme}/card.css | 0 css/{ => theme}/display.button.css | 0 css/theme/media-library.css | 33 +++++++++++++ css/{ => theme}/pager.css | 0 css/{ => theme}/tile.css | 0 css/{ => theme}/tooltip.css | 0 dsfr4drupal.info.yml | 6 +++ dsfr4drupal.libraries.yml | 49 ++++++++++++------- includes/field.theme | 12 +++++ includes/form.theme | 41 ++++++++++++++++ includes/media.theme | 18 +++++++ includes/views.theme | 35 +++++++++++++ js/{drupal.tabledrag.js => tabledrag.js} | 0 .../field/field--media--thumbnail.html.twig | 2 + .../field/field--node--created.html.twig | 2 +- templates/field/field--node--title.html.twig | 2 +- .../form/form-element--checkbox.html.twig | 26 ++++++++++ templates/form/form-element--radio.html.twig | 1 + templates/form/form-element.html.twig | 2 +- .../media/media--media-library.html.twig | 20 ++++++++ .../views/views-view--media-library.html.twig | 45 +++++++++++++++++ ...-view-unformatted--media-library.html.twig | 15 ++++++ 32 files changed, 397 insertions(+), 38 deletions(-) create mode 100644 css/component/checkbox.css create mode 100644 css/component/form.css rename css/{drupal.node.preview.css => component/node-preview.css} (100%) rename css/{drupal.tabledrag.css => component/tabledrag.css} (100%) rename css/{ => component}/toolbar.css (100%) rename css/{drupal.vertical-tabs.css => component/vertical-tabs.css} (100%) create mode 100644 css/component/views-exposed-form.css delete mode 100644 css/drupal.form.css rename css/{ => theme}/card.css (100%) rename css/{ => theme}/display.button.css (100%) create mode 100644 css/theme/media-library.css rename css/{ => theme}/pager.css (100%) rename css/{ => theme}/tile.css (100%) rename css/{ => theme}/tooltip.css (100%) create mode 100644 includes/media.theme create mode 100644 includes/views.theme rename js/{drupal.tabledrag.js => tabledrag.js} (100%) create mode 100644 templates/field/field--media--thumbnail.html.twig create mode 100644 templates/form/form-element--checkbox.html.twig create mode 100644 templates/form/form-element--radio.html.twig create mode 100644 templates/media/media--media-library.html.twig create mode 100644 templates/views/views-view--media-library.html.twig create mode 100644 templates/views/views-view-unformatted--media-library.html.twig diff --git a/components/card/card.component.yml b/components/card/card.component.yml index 6b9c967..cc6b0da 100644 --- a/components/card/card.component.yml +++ b/components/card/card.component.yml @@ -72,6 +72,9 @@ props: title: type: string title: Title + title_attributes: + type: Drupal\Core\Template\Attribute + title: Title attributes title_tag: type: string title: Title HTML tag diff --git a/components/card/card.twig b/components/card/card.twig index 1f95bf3..1b32a00 100644 --- a/components/card/card.twig +++ b/components/card/card.twig @@ -1,6 +1,7 @@ {% set attributes = attributes|default(create_attribute()) %} {% set title_tag = title_tag|default('h3') %} {% set link_attributes = link_attributes|default(create_attribute()) %} +{% set title_attributes = title_attributes|default(create_attribute()) %} {% if variant %} {% set attributes = attributes.addClass('fr-card--' ~ variant) %} @@ -32,7 +33,7 @@ <div{{ attributes.addClass('fr-card') }}> <div class="fr-card__body"> <div class="fr-card__content"> - <{{ title_tag }} class="fr-card__title"> + <{{ title_tag }}{{ title_attributes.addClass('fr-card__title') }}> {% if use_button %} <buton{{ link_attributes }}>{{ title }}</buton> {% elseif url %} diff --git a/css/component/checkbox.css b/css/component/checkbox.css new file mode 100644 index 0000000..be72fa3 --- /dev/null +++ b/css/component/checkbox.css @@ -0,0 +1,18 @@ +/** + * @file + * Manage checkbox styles. + */ + +/* Fix checkbox rendering when label is visually hidden */ +.fr-checkbox-group input[type="checkbox"] + label.visually-hidden { + width: 1.5rem; + height: 1.5rem; + margin-left: .5rem; + margin-top: .5rem; + padding-left: 1.5rem; + clip: auto; + z-index: 1; +} +.fr-checkbox-group input[type="checkbox"] + label.visually-hidden::before { + left: 0; +} diff --git a/css/component/form.css b/css/component/form.css new file mode 100644 index 0000000..51e959b --- /dev/null +++ b/css/component/form.css @@ -0,0 +1,44 @@ +/** + * @file + * Manage form styles. + */ + +:root { + --form-spacing: 1.5rem; + --form-spacing-s: 1rem; + --form-spacing-xs: .5rem; + --form-item-spacing: var(--form-spacing); + + /* Label font-size * line-height */ + --form-label-line-height: calc(1rem * 1.5rem); + /* Spacing between label and input. */ + --form-label-input-spacing: .5rem; +} + +main[role="main"] > .fr-container > form, +main[role="main"] > .fr-container--fluid > form { + margin-block: var(--form-spacing); +} + +.form-actions, +.form-item { + margin-block: var(--form-item-spacing); +} + +.views-exposed-form { + display: flex; + flex-wrap: wrap; + margin-block: var(--form-spacing); + padding: var(--form-spacing-xs) var(--form-spacing) var(--form-spacing) var(--form-spacing); + border: 1px solid var(--border-plain-grey); +} + +.fr-upload-group { + padding: var(--form-spacing-xs) var(--form-spacing) var(--form-spacing) var(--form-spacing); + border: 1px solid var(--border-plain-grey); +} + +.fr-upload-group > label { + font-weight: 700; +} + diff --git a/css/drupal.node.preview.css b/css/component/node-preview.css similarity index 100% rename from css/drupal.node.preview.css rename to css/component/node-preview.css diff --git a/css/drupal.tabledrag.css b/css/component/tabledrag.css similarity index 100% rename from css/drupal.tabledrag.css rename to css/component/tabledrag.css diff --git a/css/toolbar.css b/css/component/toolbar.css similarity index 100% rename from css/toolbar.css rename to css/component/toolbar.css diff --git a/css/drupal.vertical-tabs.css b/css/component/vertical-tabs.css similarity index 100% rename from css/drupal.vertical-tabs.css rename to css/component/vertical-tabs.css diff --git a/css/component/views-exposed-form.css b/css/component/views-exposed-form.css new file mode 100644 index 0000000..d465cc8 --- /dev/null +++ b/css/component/views-exposed-form.css @@ -0,0 +1,41 @@ +/** + * @file + * Manage styles for views exposed form. + */ + +/** + * Use flexbox and some margin resets to make the fields + actions go inline. + */ +.views-exposed-form { + display: flex; + flex-wrap: wrap; + margin-block: var(--form-spacing-s); + margin-inline: var(--form-spacing-s); + padding: var(--form-spacing-xs) var(--form-spacing-s) var(--form-spacing-s); +} + +.views-exposed-form--preview.views-exposed-form--preview { + margin-top: 0; +} + +.views-exposed-form__item { + max-width: 100%; + margin-block: var(--form-spacing-s) 0; + margin-inline: 0 var(--form-spacing-xs); +} + +.views-exposed-form .form-item--no-label, +.views-exposed-form__item.views-exposed-form__item.views-exposed-form__item--actions { + margin-block: var(--form-spacing-s) 0; + align-self: flex-end; +} + +.views-exposed-form .form-item--no-label, +.views-exposed-form__item.views-exposed-form__item--actions { + margin-top: calc(var(--form-label-line-height) + var(--form-label-input-spacing)); +} + +.views-exposed-form .fr-input-group:not(:last-child), +.views-exposed-form .fr-select-group:not(:last-child) { + margin-bottom: 0; +} diff --git a/css/drupal.form.css b/css/drupal.form.css deleted file mode 100644 index 59f76c9..0000000 --- a/css/drupal.form.css +++ /dev/null @@ -1,17 +0,0 @@ -/** - * Mange from styling with DSFR. - */ - -:root { - --form-spacing: 1.5rem; - --form-item-spacing: var(--form-spacing); -} - -main[role="main"] > .fr-container > form, -main[role="main"] > .fr-container--fluid > form { - margin-block: var(--form-spacing); -} - -.form-item { - margin-block: var(--form-item-spacing); -} diff --git a/css/card.css b/css/theme/card.css similarity index 100% rename from css/card.css rename to css/theme/card.css diff --git a/css/display.button.css b/css/theme/display.button.css similarity index 100% rename from css/display.button.css rename to css/theme/display.button.css diff --git a/css/theme/media-library.css b/css/theme/media-library.css new file mode 100644 index 0000000..6acf699 --- /dev/null +++ b/css/theme/media-library.css @@ -0,0 +1,33 @@ +/** + * @file + * Manage media library styles. + */ + +.media-library-content { + padding: 1em; +} + +.js-media-library-item { + margin-block: var(--form-item-spacing) !important; +} + +.js-media-library-add-form-added-media { + --ul-type: none; +} + +.js-media-library-add-form-added-media li { + padding: var(--form-spacing-xs) var(--form-spacing) var(--form-spacing) var(--form-spacing); + border: 1px solid var(--border-plain-grey); +} + +.js-media-library-add-form-added-media img { + display: block; + margin: 0 auto; + max-width: 100%; + height: auto; +} + +.js-media-library-add-form-added-media .form-item-name label { + display: inline-block; + font-weight: 700; +} diff --git a/css/pager.css b/css/theme/pager.css similarity index 100% rename from css/pager.css rename to css/theme/pager.css diff --git a/css/tile.css b/css/theme/tile.css similarity index 100% rename from css/tile.css rename to css/theme/tile.css diff --git a/css/tooltip.css b/css/theme/tooltip.css similarity index 100% rename from css/tooltip.css rename to css/theme/tooltip.css diff --git a/dsfr4drupal.info.yml b/dsfr4drupal.info.yml index 237e4af..30ba970 100644 --- a/dsfr4drupal.info.yml +++ b/dsfr4drupal.info.yml @@ -44,6 +44,10 @@ libraries-extend: - dsfr4drupal/drupal.tabledrag core/drupal.vertical-tabs: - dsfr4drupal/drupal.vertical-tabs + media_library/view: + - dsfr4drupal/media_library.theme + media_library/widget: + - dsfr4drupal/media_library.theme navigation/navigation.layout: - dsfr4drupal/navigation.layout node/drupal.node.preview: @@ -54,6 +58,8 @@ libraries-extend: - dsfr4drupal/tarteaucitron toolbar/toolbar: - dsfr4drupal/toolbar + views/views.module: + - dsfr4drupal/views libraries-override: tacjs/tarteaucitron.js: diff --git a/dsfr4drupal.libraries.yml b/dsfr4drupal.libraries.yml index 0857cec..bdaf068 100644 --- a/dsfr4drupal.libraries.yml +++ b/dsfr4drupal.libraries.yml @@ -62,7 +62,7 @@ component.card: component: /libraries/dsfr/dist/component/card/card.min.css: { minified: true } theme: - css/card.css: {} + css/theme/card.css: {} dependencies: - dsfr4drupal/component.button - dsfr4drupal/core @@ -71,6 +71,7 @@ component.checkbox: css: component: /libraries/dsfr/dist/component/checkbox/checkbox.min.css: { minified: true } + css/component/checkbox.css: {} dependencies: - dsfr4drupal/component.form - dsfr4drupal/core @@ -111,7 +112,7 @@ component.display: component.display.button: css: theme: - css/display.button.css: {} + css/theme/display.button.css: {} component.connect: css: @@ -242,7 +243,7 @@ component.pagination: component: /libraries/dsfr/dist/component/pagination/pagination.min.css: { minified: true } theme: - css/pager.css: {} + css/theme/pager.css: {} dependencies: - dsfr4drupal/core @@ -425,7 +426,7 @@ component.tile: component: /libraries/dsfr/dist/component/tile/tile.min.css: { minified: true } theme: - css/tile.css: {} + css/theme/tile.css: {} dependencies: - dsfr4drupal/core @@ -452,7 +453,7 @@ component.tooltip: component: /libraries/dsfr/dist/component/tooltip/tooltip.min.css: { minified: true } theme: - css/tooltip.css: {} + css/theme/tooltip.css: {} js: /libraries/dsfr/dist/component/tooltip/tooltip.module.min.js: minified: true @@ -547,8 +548,8 @@ core: drupal.form: css: - theme: - css/drupal.form.css: {} + component: + css/component/form.css: {} drupal.message: js: @@ -558,20 +559,20 @@ drupal.message: drupal.node.preview: css: - theme: - css/drupal.node.preview.css: {} + component: + css/component/node-preview.css: {} drupal.tabledrag: css: - theme: - css/drupal.tabledrag.css: {} + component: + css/component/tabledrag.css: {} js: - js/drupal.tabledrag.js: {} + js/tabledrag.js: {} drupal.vertical-tabs: css: - theme: - css/drupal.vertical-tabs.css: {} + component: + css/component/vertical-tabs.css: {} #legacy: # js: @@ -582,10 +583,17 @@ drupal.vertical-tabs: # # Move DSFR modules to first load to improve Javascript file aggregation. # weight: -50 +media_library.theme: + css: + theme: + css/theme/media-library.css: {} + dependencies: + - dsfr4drupal/core + navigation.layout: css: theme: - css/navigation.header.css: {} + css/theme/navigation.header.css: {} scheme: css: @@ -608,8 +616,8 @@ tarteaucitron: toolbar: css: - theme: - css/toolbar.css: {} + component: + css/component/toolbar.css: {} utility: css: @@ -622,3 +630,10 @@ utility.icons: css: base: /libraries/dsfr/dist/utility/icons/icons.min.css: { minified: true } + +views: + css: + component: + css/component/views-exposed-form.css: {} + dependencies: + - dsfr4drupal/drupal.form diff --git a/includes/field.theme b/includes/field.theme index a9988cd..f1dec96 100644 --- a/includes/field.theme +++ b/includes/field.theme @@ -32,3 +32,15 @@ function dsfr4drupal_preprocess_field(array &$variables): void { } } } + +/** + * Implements hook_preprocess_HOOK() for "field__media__thumbnail". + */ +function dsfr4drupal_preprocess_field__media__thumbnail(array &$variables): void { + $items = &$variables['items']; + + // Add "fr-responsive-img" by default. + foreach ($items as &$item) { + $item['content']['#item_attributes']['class'][] = 'fr-responsive-img'; + } +} diff --git a/includes/form.theme b/includes/form.theme index b2a2ebf..d639458 100644 --- a/includes/form.theme +++ b/includes/form.theme @@ -9,6 +9,7 @@ declare(strict_types=1); use Drupal\Component\Utility\Crypt; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Render\Element; /** * Implements hook_form_FORM_ID_alter() for "node_preview_form_select". @@ -58,6 +59,29 @@ function dsfr4drupal_preprocess_form(array &$variables): void { } } +/** + * Implements hook_preprocess_HOOK() for "views_exposed_form". + */ +function dsfr4drupal_preprocess_views_exposed_form(&$variables) { + $form = &$variables['form']; + + // Add BEM classes for items in the form. + // Sorted keys. + foreach (Element::children($form, TRUE) as $child_key) { + if (!empty($form[$child_key]['#type'])) { + if ($form[$child_key]['#type'] === 'actions') { + // We need the key of the element that precedes the actions element. + $form[$child_key]['#attributes']['class'][] = 'views-exposed-form__item'; + $form[$child_key]['#attributes']['class'][] = 'views-exposed-form__item--actions'; + } + + if (!in_array($form[$child_key]['#type'], ['hidden', 'actions'])) { + $form[$child_key]['#wrapper_attributes']['class'][] = 'views-exposed-form__item'; + } + } + } +} + /** * Implements hook_preprocess_HOOK() for "webform". */ @@ -253,6 +277,23 @@ function dsfr4drupal_preprocess_textarea(array &$variables): void { } } +/** + * Implements hook_theme_suggestions_HOOK_alter() for "form_elemtn". + */ +function dsfr4drupal_theme_suggestions_form_element_alter(array &$suggestions, array $variables): void { + if (empty($suggestions)) { + $suggestions[] = 'form_element'; + } + + // Set suggestions by form element type. + $new_suggestions = []; + foreach ($suggestions as $suggestion) { + $new_suggestions[] = $suggestion; + $new_suggestions[] = $suggestion . '__' . $variables['element']['#type']; + } + $suggestions = $new_suggestions; +} + /** * Implements hook_theme_suggestions_HOOK_alter() for "input". */ diff --git a/includes/media.theme b/includes/media.theme new file mode 100644 index 0000000..d98dfcb --- /dev/null +++ b/includes/media.theme @@ -0,0 +1,18 @@ +<?php + +/** + * @file + * Functions to support media theming in the "DSFR for Drupal" theme. + */ + +declare(strict_types=1); + +/** + * Implements hook_preprocss_hook() for "media__media_library". + */ +function dsfr4drupal_preprocess_media__media_library(array &$variables): void { + /** @var \Drupal\media\MediaInterface $media */ + $media = $variables['media']; + + +} diff --git a/includes/views.theme b/includes/views.theme new file mode 100644 index 0000000..176e8f4 --- /dev/null +++ b/includes/views.theme @@ -0,0 +1,35 @@ +<?php + +/** + * @file + * Functions to support views theming in the "DSFR for Drupal" theme. + */ + +declare(strict_types=1); + +/** + * Implements hook_preprocss_hook() for "views_view__media_library". + */ +function dsfr4drupal_preprocess_views_view__media_library(array &$variables): void { + if (empty($variables['header'])) { + return; + } + + foreach ($variables['header'] as &$header) { + $options = &$header['#options']; + + // Add tab component attributes. + $options['attributes']['aria-selected'] = 'false'; + $options['attributes']['class'][] = 'fr-tabs__tab'; + $options['attributes']['role'] = 'tab'; + + // Set tab active. + if ($options['view']->current_display === $options['target_display_id']) { + $options['attributes']['aria-selected'] = 'true'; + $options['attributes']['class'][] = 'fr-tabs__tab--selected'; + } + } + + $variables['#attached']['library'][] = 'dsfr4drupal/component.tab'; + $variables['#attached']['library'][] = 'dsfr4drupal/component.link'; +} diff --git a/js/drupal.tabledrag.js b/js/tabledrag.js similarity index 100% rename from js/drupal.tabledrag.js rename to js/tabledrag.js diff --git a/templates/field/field--media--thumbnail.html.twig b/templates/field/field--media--thumbnail.html.twig new file mode 100644 index 0000000..df508d3 --- /dev/null +++ b/templates/field/field--media--thumbnail.html.twig @@ -0,0 +1,2 @@ +{# Include default "field" template without alter it to benefits of preprocessing. #} +{% include 'field.html.twig' %} diff --git a/templates/field/field--node--created.html.twig b/templates/field/field--node--created.html.twig index 8207766..2e5679c 100644 --- a/templates/field/field--node--created.html.twig +++ b/templates/field/field--node--created.html.twig @@ -1,5 +1,5 @@ {% if not is_inline %} - {% include "field.html.twig" %} + {% include 'field.html.twig' %} {% else %} {%- if attributes is not empty -%} <span{{ attributes }}> diff --git a/templates/field/field--node--title.html.twig b/templates/field/field--node--title.html.twig index 8207766..2e5679c 100644 --- a/templates/field/field--node--title.html.twig +++ b/templates/field/field--node--title.html.twig @@ -1,5 +1,5 @@ {% if not is_inline %} - {% include "field.html.twig" %} + {% include 'field.html.twig' %} {% else %} {%- if attributes is not empty -%} <span{{ attributes }}> diff --git a/templates/form/form-element--checkbox.html.twig b/templates/form/form-element--checkbox.html.twig new file mode 100644 index 0000000..ebc4eb6 --- /dev/null +++ b/templates/form/form-element--checkbox.html.twig @@ -0,0 +1,26 @@ +{% set classes = [ + 'js-form-item', + 'form-item', + 'js-form-type-' ~ type|clean_class, + 'form-item-' ~ name|clean_class, + 'js-form-item-' ~ name|clean_class, + title_display not in ['after', 'before'] ? 'form-item--no-label', + disabled == 'disabled' ? 'form-disabled', +] %} + +<div{{ attributes.addClass(classes) }}> + {% if prefix is not empty %} + <span class="field-prefix">{{ prefix }}</span> + {% endif %} + + {{ children }} + {% if suffix is not empty %} + <span class="field-suffix">{{ suffix }}</span> + {% endif %} + {# The label is always display after to display checkbox. #} + {{ label }} + {# In case of we need to display errors in form element. #} + {% if show_error %} + {% include '@dsfr4drupal/templates/form/form-element.error.html.twig' %} + {% endif %} +</div> diff --git a/templates/form/form-element--radio.html.twig b/templates/form/form-element--radio.html.twig new file mode 100644 index 0000000..74456c6 --- /dev/null +++ b/templates/form/form-element--radio.html.twig @@ -0,0 +1 @@ +{% include 'form-element--checkbox.html.twig' %} diff --git a/templates/form/form-element.html.twig b/templates/form/form-element.html.twig index adee85c..c436fac 100644 --- a/templates/form/form-element.html.twig +++ b/templates/form/form-element.html.twig @@ -4,7 +4,7 @@ 'js-form-type-' ~ type|clean_class, 'form-item-' ~ name|clean_class, 'js-form-item-' ~ name|clean_class, - title_display not in ['after', 'before'] ? 'form-no-label', + title_display not in ['after', 'before'] ? 'form-item--no-label', disabled == 'disabled' ? 'form-disabled', ] %} diff --git a/templates/media/media--media-library.html.twig b/templates/media/media--media-library.html.twig new file mode 100644 index 0000000..9f70bd2 --- /dev/null +++ b/templates/media/media--media-library.html.twig @@ -0,0 +1,20 @@ +{% if attributes is not empty %} +<div{{ attributes }}> +{% endif %} + + <div{{ preview_attributes.addClass('js-media-library-item-preview') }}> + {{ include('dsfr4drupal:card', { + 'image': content.thumbnail ? content.thumbnail : '', + 'title': name, + 'title_attributes': metadata_attributes, + 'description': content|without('thumbnail'), + 'no_arrow': true, + 'detail': not status ? 'unpublished'|t : '', + 'detail_icon': 'fr-icon-warning-fill', + 'variant': 'sm', + }, with_context=false) }} + </div> + +{% if attributes is not empty %} + </div> +{% endif %} diff --git a/templates/views/views-view--media-library.html.twig b/templates/views/views-view--media-library.html.twig new file mode 100644 index 0000000..0b66c96 --- /dev/null +++ b/templates/views/views-view--media-library.html.twig @@ -0,0 +1,45 @@ + +{% + set classes = [ + dom_id ? 'js-view-dom-id-' ~ dom_id, +] +%} +<div{{ attributes.addClass(classes) }}> + {{ title_prefix }} + {{ title }} + {{ title_suffix }} + + {% if header %} + <header class="fr-tabs"> + <ul class="fr-tabs__list" role="tablist"> + {% for link in header %} + <li role="presentation"> + {{ link }} + </li> + {% endfor %} + </ul> + </header> + {{ attach_library('dsfr4drupal/component.tab') }} + {% endif %} + + {{ exposed }} + {{ attachment_before }} + + {% if rows -%} + {{ rows }} + {% elseif empty -%} + {{ empty }} + {% endif %} + {{ pager }} + + {{ attachment_after }} + {{ more }} + + {% if footer %} + <footer> + {{ footer }} + </footer> + {% endif %} + + {{ feed_icons }} +</div> diff --git a/templates/views/views-view-unformatted--media-library.html.twig b/templates/views/views-view-unformatted--media-library.html.twig new file mode 100644 index 0000000..61fa26d --- /dev/null +++ b/templates/views/views-view-unformatted--media-library.html.twig @@ -0,0 +1,15 @@ +{% set row_classes = [ + default_row_class ? 'views-row', +] %} + +{% if title %} + <h3>{{ title }}</h3> +{% endif %} + +<div class="fr-grid-row fr-grid-row--gutters"> + {% for row in rows %} + <div{{ row.attributes.addClass(row_classes).addClass(['fr-col-12', 'fr-col-sm-6', 'fr-col-md-4', 'fr-col-lg-3']) }}> + {{- row.content -}} + </div> + {% endfor %} +</div> -- GitLab From 4d518fd0dbab881097c2afd18a227aea65a70196 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20BRINDLE?= <sebastien.brindle@kleegroup.com> Date: Sat, 25 Jan 2025 19:11:56 +0100 Subject: [PATCH 05/45] Increase special inputs rendering. --- css/component/form.css | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/css/component/form.css b/css/component/form.css index 51e959b..176aa97 100644 --- a/css/component/form.css +++ b/css/component/form.css @@ -42,3 +42,11 @@ main[role="main"] > .fr-container--fluid > form { font-weight: 700; } +.js-filter-wrapper { + margin-top: calc(var(--form-item-spacing) * -.75); +} + +.js input.form-autocomplete { + padding-right: 2.5rem; + background-position: calc(100% - 1rem) 50%; +} -- GitLab From 77e5fefd9483501e0e910ca82bb8170083af475a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20BRINDLE?= <sebastien.brindle@kleegroup.com> Date: Sat, 25 Jan 2025 20:43:12 +0100 Subject: [PATCH 06/45] Manage styles for UI dialog. --- css/component/checkbox.css | 2 +- css/component/form.css | 6 ++-- css/component/node-preview.css | 4 +-- css/component/tabledrag.css | 2 +- css/component/toolbar.css | 2 +- css/component/ui-dialog.css | 59 +++++++++++++++++++++++++++++++++ css/component/vertical-tabs.css | 2 +- css/theme/card.css | 2 +- css/theme/media-library.css | 4 +-- css/theme/pager.css | 2 +- css/theme/tile.css | 2 +- css/theme/tooltip.css | 2 +- dsfr4drupal.info.yml | 2 ++ dsfr4drupal.libraries.yml | 6 ++++ 14 files changed, 82 insertions(+), 15 deletions(-) create mode 100644 css/component/ui-dialog.css diff --git a/css/component/checkbox.css b/css/component/checkbox.css index be72fa3..f41a5f5 100644 --- a/css/component/checkbox.css +++ b/css/component/checkbox.css @@ -1,6 +1,6 @@ /** * @file - * Manage checkbox styles. + * Manage styles for checkbox. */ /* Fix checkbox rendering when label is visually hidden */ diff --git a/css/component/form.css b/css/component/form.css index 176aa97..c85a3f4 100644 --- a/css/component/form.css +++ b/css/component/form.css @@ -1,6 +1,6 @@ /** * @file - * Manage form styles. + * Manage styles for form. */ :root { @@ -30,12 +30,12 @@ main[role="main"] > .fr-container--fluid > form { flex-wrap: wrap; margin-block: var(--form-spacing); padding: var(--form-spacing-xs) var(--form-spacing) var(--form-spacing) var(--form-spacing); - border: 1px solid var(--border-plain-grey); + border: 1px solid var(--border-default-grey); } .fr-upload-group { padding: var(--form-spacing-xs) var(--form-spacing) var(--form-spacing) var(--form-spacing); - border: 1px solid var(--border-plain-grey); + border: 1px solid var(--border-default-grey); } .fr-upload-group > label { diff --git a/css/component/node-preview.css b/css/component/node-preview.css index bf08321..860c39f 100644 --- a/css/component/node-preview.css +++ b/css/component/node-preview.css @@ -1,6 +1,6 @@ /** * @file - * Manage node preview styling. + * Manage styles for node preview. */ .node-preview-container { @@ -8,7 +8,7 @@ background-position: 0 100%; background-repeat: no-repeat; background-size: 100% .25rem; - background-image: linear-gradient(0deg,var(--border-plain-grey),var(--border-plain-grey)); + background-image: linear-gradient(0deg,var(--border-default-grey),var(--border-default-grey)); padding: 2rem 2rem 2.25rem; /* Based on DSFR header z-index. */ z-index: calc(var(--ground) + 751); diff --git a/css/component/tabledrag.css b/css/component/tabledrag.css index 55733fc..90a7957 100644 --- a/css/component/tabledrag.css +++ b/css/component/tabledrag.css @@ -1,6 +1,6 @@ /** * @file - * Manage tabledrag styling. + * Manage styles for tabledrag. */ a.tabledrag-handle[href] { diff --git a/css/component/toolbar.css b/css/component/toolbar.css index 1989351..235df43 100644 --- a/css/component/toolbar.css +++ b/css/component/toolbar.css @@ -1,6 +1,6 @@ /** * @file - * Fix toolbar styling. + * Manage styles for toolbar. */ .toolbar a { diff --git a/css/component/ui-dialog.css b/css/component/ui-dialog.css new file mode 100644 index 0000000..50972f9 --- /dev/null +++ b/css/component/ui-dialog.css @@ -0,0 +1,59 @@ +/** + * @file + * Manage styles for UI dialog. + */ + +.ui-dialog { + padding: 0; +} + +.ui-dialog .ui-dialog-titlebar { + padding: calc(var(--form-spacing) / 2) var(--form-spacing); +} + +.ui-widget-header { + margin: -1px -1px 0 -1px; + background-color: var(--background-flat-blue-france); + color: var(--text-inverted-blue-france); + font-weight: 700; + border: 0; +} + +.ui-widget-content { + background: var(--background-default-grey); + color: var(--text-default-grey); +} +.ui-widget.ui-widget-content { + border: 1px solid var(--border-default-grey); +} + +.ui-dialog .ui-dialog-titlebar-close { + --icon-size: 2rem; + + width: var(--icon-size); + height: var(--icon-size); + margin-top: calc(var(--icon-size) / -2); + right: var(--form-spacing); + background: none; + color: var(--text-inverted-blue-france); + border: 0; + opacity: .8; + transition: opacity .25s; + overflow: hidden; +} +.ui-dialog .ui-dialog-titlebar-close:hover { + background: none; + opacity: 1; +} + +.ui-dialog .ui-dialog-titlebar-close .ui-button-icon { + position: static; + display: block; + width: 100%; + height: 100%; + margin: 0; + background-image: none; + background-color: currentColor; + mask-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCI+PHBhdGggZD0ibTEyIDEwLjYgNC45NS00Ljk2IDEuNCAxLjRMMTMuNDIgMTJsNC45NiA0Ljk1LTEuNCAxLjRMMTIgMTMuNDJsLTQuOTUgNC45Ni0xLjQtMS40TDEwLjU4IDEyIDUuNjMgNy4wNWwxLjQtMS40eiIvPjwvc3ZnPg==); + mask-size: contain; +} diff --git a/css/component/vertical-tabs.css b/css/component/vertical-tabs.css index be39efc..b2decff 100644 --- a/css/component/vertical-tabs.css +++ b/css/component/vertical-tabs.css @@ -1,6 +1,6 @@ /** * @file - * Manage vertical tabs styling. + * Manage styles for vertical tabs. */ .vertical-tabs__menu [href] { diff --git a/css/theme/card.css b/css/theme/card.css index 4792584..32f5bf9 100644 --- a/css/theme/card.css +++ b/css/theme/card.css @@ -1,6 +1,6 @@ /** * @file - * Fix card styling with "time" html tag. + * Fix styles for card with "time" html tag. */ .fr-card__detail time { diff --git a/css/theme/media-library.css b/css/theme/media-library.css index 6acf699..906dc2a 100644 --- a/css/theme/media-library.css +++ b/css/theme/media-library.css @@ -1,6 +1,6 @@ /** * @file - * Manage media library styles. + * Manage styles for media library. */ .media-library-content { @@ -17,7 +17,7 @@ .js-media-library-add-form-added-media li { padding: var(--form-spacing-xs) var(--form-spacing) var(--form-spacing) var(--form-spacing); - border: 1px solid var(--border-plain-grey); + border: 1px solid var(--border-default-grey); } .js-media-library-add-form-added-media img { diff --git a/css/theme/pager.css b/css/theme/pager.css index a1aa43f..ca2627c 100644 --- a/css/theme/pager.css +++ b/css/theme/pager.css @@ -1,6 +1,6 @@ /** * @file - * Fix pagination to allow AJAX use. + * Fix styles for pagination to allow AJAX use. */ .fr-pagination__link[href=""], diff --git a/css/theme/tile.css b/css/theme/tile.css index 494eca3..13c9ba4 100644 --- a/css/theme/tile.css +++ b/css/theme/tile.css @@ -1,6 +1,6 @@ /** * @file - * Fix card styling with "time" html tag. + * Fix styles for tile with "time" html tag. */ .fr-tile__detail time { diff --git a/css/theme/tooltip.css b/css/theme/tooltip.css index c7fac6d..9d75cb1 100644 --- a/css/theme/tooltip.css +++ b/css/theme/tooltip.css @@ -1,6 +1,6 @@ /** * @file - * Fix tooltip styling. + * Fix styles for tooltip. */ .fr-btn--tooltip { diff --git a/dsfr4drupal.info.yml b/dsfr4drupal.info.yml index 30ba970..8d38db5 100644 --- a/dsfr4drupal.info.yml +++ b/dsfr4drupal.info.yml @@ -36,6 +36,8 @@ libraries: - dsfr4drupal/utility libraries-extend: + core/drupal.dialog: + - dsfr4drupal/drupal.dialog core/drupal.form: - dsfr4drupal/drupal.form core/drupal.message: diff --git a/dsfr4drupal.libraries.yml b/dsfr4drupal.libraries.yml index bdaf068..0176457 100644 --- a/dsfr4drupal.libraries.yml +++ b/dsfr4drupal.libraries.yml @@ -546,6 +546,12 @@ core: production: true verbose: true +drupal.dialog: + css: + component: + # Need to fix weight to 99 to override "Gin toolbar" styles. + css/component/ui-dialog.css: { weight: 100 } + drupal.form: css: component: -- GitLab From c36f3392e028df5e609396dbad7cbb74feec8c21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20BRINDLE?= <sebastien.brindle@kleegroup.com> Date: Sun, 26 Jan 2025 10:35:28 +0100 Subject: [PATCH 07/45] Reset Drupal custom form styles for DSFR default forms. --- css/component/form.css | 11 +++++++++-- css/{component => theme}/ui-dialog.css | 0 dsfr4drupal.libraries.yml | 4 ++-- 3 files changed, 11 insertions(+), 4 deletions(-) rename css/{component => theme}/ui-dialog.css (100%) diff --git a/css/component/form.css b/css/component/form.css index c85a3f4..a84309d 100644 --- a/css/component/form.css +++ b/css/component/form.css @@ -5,8 +5,8 @@ :root { --form-spacing: 1.5rem; - --form-spacing-s: 1rem; - --form-spacing-xs: .5rem; + --form-spacing-s: calc(var(--form-spacing) * .67); + --form-spacing-xs: calc(var(--form-spacing) / 3); --form-item-spacing: var(--form-spacing); /* Label font-size * line-height */ @@ -15,6 +15,13 @@ --form-label-input-spacing: .5rem; } +/* Unset form spacing for default DSFR forms. */ +.fr-follow__newsletter, +.fr-search-bar { + --form-spacing: 0; + --form-item-spacing: 0; +} + main[role="main"] > .fr-container > form, main[role="main"] > .fr-container--fluid > form { margin-block: var(--form-spacing); diff --git a/css/component/ui-dialog.css b/css/theme/ui-dialog.css similarity index 100% rename from css/component/ui-dialog.css rename to css/theme/ui-dialog.css diff --git a/dsfr4drupal.libraries.yml b/dsfr4drupal.libraries.yml index 0176457..7509194 100644 --- a/dsfr4drupal.libraries.yml +++ b/dsfr4drupal.libraries.yml @@ -548,9 +548,9 @@ core: drupal.dialog: css: - component: + theme: # Need to fix weight to 99 to override "Gin toolbar" styles. - css/component/ui-dialog.css: { weight: 100 } + css/theme/ui-dialog.css: { weight: 100 } drupal.form: css: -- GitLab From 722e8cae3e1a6bab96bf6b0288d13a603b6dccf8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20BRINDLE?= <sebastien.brindle@kleegroup.com> Date: Sun, 26 Jan 2025 10:51:32 +0100 Subject: [PATCH 08/45] Increase transcription management. --- components/modal/modal.component.yml | 6 ++++++ components/modal/modal.twig | 6 +++--- components/transcription/transcription.component.yml | 10 ++++++++++ components/transcription/transcription.twig | 9 ++++++--- 4 files changed, 25 insertions(+), 6 deletions(-) diff --git a/components/modal/modal.component.yml b/components/modal/modal.component.yml index 183171f..a3f2974 100644 --- a/components/modal/modal.component.yml +++ b/components/modal/modal.component.yml @@ -15,6 +15,12 @@ props: type: string title: Button title attribute description: 'By default: "Close", or the translated string in another language.' + content: + type: string + title: Content + footer: + type: string + title: Footer html_id: type: string title: HTML identifier diff --git a/components/modal/modal.twig b/components/modal/modal.twig index 6acc70c..6bef3ef 100644 --- a/components/modal/modal.twig +++ b/components/modal/modal.twig @@ -20,13 +20,13 @@ {% endblock modal_title %} </div> {% block modal_content %} - <p>{{ 'Default content.'|t }}</p> + {{ content }} {% endblock modal_content %} </div> - {% if modal_footer or block('modal_footer') is defined %} + {% if footer or block('modal_footer') is defined %} <div class="fr-modal__footer"> {% block modal_footer %} - {{ modal_footer }} + {{ footer }} {% endblock modal_footer %} </div> {% endif %} diff --git a/components/transcription/transcription.component.yml b/components/transcription/transcription.component.yml index 3c89bd4..0c0f6d8 100644 --- a/components/transcription/transcription.component.yml +++ b/components/transcription/transcription.component.yml @@ -7,10 +7,20 @@ props: attributes: type: Drupal\Core\Template\Attribute title: Attributes + button_label: + type: string + title: Button label + button_title: + type: string + title: Button title attribute content: type: string title: Content description: Display into transcription modal. + expanded: + type: boolean + title: Is expanded? + default: false modal_id: type: string title: Modal identifier diff --git a/components/transcription/transcription.twig b/components/transcription/transcription.twig index e47af06..150816e 100644 --- a/components/transcription/transcription.twig +++ b/components/transcription/transcription.twig @@ -1,20 +1,23 @@ {% set attributes = attributes|default(create_attribute()) %} +{% set button_label = button_label|default('Enlarge'|t) %} +{% set button_title = button_title|default('Enlarge transcription'|t) %} {% set title = title|default('Transcription'|t) %} {% set transcription_id = transcription_id|default('transcription-' ~ random()) %} {% set collapse_id = 'fr-transcription-collapse-' ~ transcription_id %} {% set modal_id = 'fr-transcription-modal-' ~ transcription_id %} {% set modal_title = modal_title|default(title) %} +{% set expanded = expanded ?? false %} <div{{ attributes.addClass('fr-transcription') }}> - <button type="button" class="fr-transcription__btn" aria-expanded="false" aria-controls="{{ collapse_id }}"> + <button type="button" class="fr-transcription__btn" aria-expanded="{{ expanded ? 'true': 'false' }}" aria-controls="{{ collapse_id }}"> {{ title }} </button> <div class="fr-collapse" id="{{ collapse_id }}"> <div class="fr-transcription__footer"> <div class="fr-transcription__actions-group"> - <button type="button" class="fr-btn fr-btn--fullscreen" aria-controls="{{ modal_id }}" data-fr-opened="false" title="{{ 'Enlarge transcription'|t }}"> - {{ 'Enlarge'|t }} + <button type="button" class="fr-btn fr-btn--fullscreen" aria-controls="{{ modal_id }}" data-fr-opened="false" title="{{ button_title }}"> + {{ button_label }} </button> </div> </div> -- GitLab From ee91560a19de2299226bf0ce5b15f5846e3638b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20BRINDLE?= <sebastien.brindle@kleegroup.com> Date: Sun, 26 Jan 2025 11:32:38 +0100 Subject: [PATCH 09/45] Fix modal footer display. --- components/modal/modal.component.yml | 5 ++++- components/modal/modal.twig | 8 +++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/components/modal/modal.component.yml b/components/modal/modal.component.yml index a3f2974..e2dff3c 100644 --- a/components/modal/modal.component.yml +++ b/components/modal/modal.component.yml @@ -27,10 +27,13 @@ props: title: type: string title: Title + title_tag: + type: string + title: Title HTML tag + default: h1 slots: modal_content: title: Modal content - required: true modal_footer: title: Modal footer modal_title: diff --git a/components/modal/modal.twig b/components/modal/modal.twig index 6bef3ef..652e233 100644 --- a/components/modal/modal.twig +++ b/components/modal/modal.twig @@ -2,6 +2,7 @@ {% set button_label = button_label|default('Close'|t) %} {% set button_title = button_title|default('Close'|t) %} {% set html_id = html_id|default('modal-default') %} +{% set title_tag = title_tag|default('h1') %} <dialog id="{{ html_id }}" class="fr-modal" role="dialog" aria-labelledby="{{ html_id ~ '-title' }}"> <div class="fr-container fr-container--fluid fr-container-md"> @@ -14,16 +15,17 @@ </button> </div> <div class="fr-modal__content"> - <div id="{{ html_id ~ '-title' }}" class="fr-modal__title"> + <{{ title_tag }} id="{{ html_id ~ '-title' }}" class="fr-modal__title"> {% block modal_title %} {{ title }} {% endblock modal_title %} - </div> + </{{ title_tag }}> {% block modal_content %} {{ content }} {% endblock modal_content %} </div> - {% if footer or block('modal_footer') is defined %} + + {% if footer or (block('modal_footer') is defined and block('modal_footer')|trim is not empty) %} <div class="fr-modal__footer"> {% block modal_footer %} {{ footer }} -- GitLab From 6ae48b87b7d7c2a4be8701c5f9bd5d6783c05e2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20BRINDLE?= <sebastien.brindle@kleegroup.com> Date: Sun, 26 Jan 2025 12:07:20 +0100 Subject: [PATCH 10/45] Try as much as possible to style UI Dialog like DSFR modal. --- css/component/form.css | 3 +- css/theme/media-library.css | 5 ++ css/theme/ui-dialog.css | 91 +++++++++++++++++++++++++------------ 3 files changed, 69 insertions(+), 30 deletions(-) diff --git a/css/component/form.css b/css/component/form.css index a84309d..1a7765f 100644 --- a/css/component/form.css +++ b/css/component/form.css @@ -28,7 +28,8 @@ main[role="main"] > .fr-container--fluid > form { } .form-actions, -.form-item { +.form-item, +.form-wrapper { margin-block: var(--form-item-spacing); } diff --git a/css/theme/media-library.css b/css/theme/media-library.css index 906dc2a..08498a8 100644 --- a/css/theme/media-library.css +++ b/css/theme/media-library.css @@ -7,6 +7,11 @@ padding: 1em; } +/* Remove margin at the end of the form */ +.media-library-view .views-form { + margin-bottom: calc(var(--form-spacing) * -1); +} + .js-media-library-item { margin-block: var(--form-item-spacing) !important; } diff --git a/css/theme/ui-dialog.css b/css/theme/ui-dialog.css index 50972f9..f8e44b7 100644 --- a/css/theme/ui-dialog.css +++ b/css/theme/ui-dialog.css @@ -1,22 +1,28 @@ /** * @file * Manage styles for UI dialog. + * Try as much as possible to reproduce the styles of the DSFR modal. */ .ui-dialog { - padding: 0; + padding: 0; max-height: 80vh !important; + filter: drop-shadow(var(--lifted-shadow)); } .ui-dialog .ui-dialog-titlebar { - padding: calc(var(--form-spacing) / 2) var(--form-spacing); + margin: 0; + padding: 4rem 2rem 0; } -.ui-widget-header { - margin: -1px -1px 0 -1px; - background-color: var(--background-flat-blue-france); - color: var(--text-inverted-blue-france); - font-weight: 700; - border: 0; +.ui-dialog .ui-dialog-title { + --title-spacing: 0 0 1rem 0; + + margin: var(--title-spacing); +} + +.ui-dialog .ui-dialog-content { + margin-bottom: 4rem; + padding: 0 2rem; } .ui-widget-content { @@ -24,36 +30,63 @@ color: var(--text-default-grey); } .ui-widget.ui-widget-content { - border: 1px solid var(--border-default-grey); + border: 0; } -.ui-dialog .ui-dialog-titlebar-close { - --icon-size: 2rem; +.ui-widget-header { + background: none; + border: 0; + color: var(--text-title-grey); + font-size: 1.375rem; + font-weight: 700; +} - width: var(--icon-size); - height: var(--icon-size); - margin-top: calc(var(--icon-size) / -2); - right: var(--form-spacing); +@media (min-width: 48em) { + .ui-widget-header { + font-size: 1.5rem; + line-height: 2rem; + } +} + +.ui-dialog .ui-dialog-titlebar-close { + top: 1rem; + right: 2rem; + margin: 0; + padding: .25rem .75rem; + width: auto; + height: auto; + min-height: 2rem; background: none; - color: var(--text-inverted-blue-france); border: 0; - opacity: .8; - transition: opacity .25s; - overflow: hidden; + text-align: right; + text-indent: initial; + color: var(--text-action-high-blue-france); + font-size: .875rem; + font-weight: 500; + line-height: 1.5rem; + overflow: initial; } .ui-dialog .ui-dialog-titlebar-close:hover { - background: none; - opacity: 1; + background-color: var(--hover-tint); } -.ui-dialog .ui-dialog-titlebar-close .ui-button-icon { - position: static; - display: block; - width: 100%; - height: 100%; - margin: 0; - background-image: none; +.ui-dialog .ui-dialog-titlebar-close::after { + --icon-size: 1rem; + background-color: currentColor; + content: ''; + display: inline-block; + flex: 0 0 auto; + height: var(--icon-size); + margin-left: .5rem; + margin-right: -.125rem; mask-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCI+PHBhdGggZD0ibTEyIDEwLjYgNC45NS00Ljk2IDEuNCAxLjRMMTMuNDIgMTJsNC45NiA0Ljk1LTEuNCAxLjRMMTIgMTMuNDJsLTQuOTUgNC45Ni0xLjQtMS40TDEwLjU4IDEyIDUuNjMgNy4wNWwxLjQtMS40eiIvPjwvc3ZnPg==); - mask-size: contain; + mask-size: 100% 100%; + vertical-align: calc((.75em - var(--icon-size))*.5); + width: var(--icon-size); +} + +.ui-dialog .ui-dialog-titlebar-close .ui-button-icon, +.ui-dialog .ui-dialog-titlebar-close .ui-button-icon-space { + display: none; } -- GitLab From 171cf5197cdd8ce92dcce09d8d717c074b690958 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20BRINDLE?= <sebastien.brindle@kleegroup.com> Date: Sun, 26 Jan 2025 12:11:18 +0100 Subject: [PATCH 11/45] Provide a custom class to remove form spacing. --- css/component/form.css | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/css/component/form.css b/css/component/form.css index 1a7765f..5f0ee52 100644 --- a/css/component/form.css +++ b/css/component/form.css @@ -15,6 +15,12 @@ --form-label-input-spacing: .5rem; } +/* Provide a class to remove spacing. */ +.dsfr4drupal-form--no-spacing { + --form-spacing: 0; + --form-item-spacing: 0; +} + /* Unset form spacing for default DSFR forms. */ .fr-follow__newsletter, .fr-search-bar { -- GitLab From 648f0d701eadf4b327c507a932578487be1edb00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20BRINDLE?= <sebastien.brindle@kleegroup.com> Date: Sun, 26 Jan 2025 12:12:17 +0100 Subject: [PATCH 12/45] Fix missing file. --- css/{ => theme}/navigation.header.css | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename css/{ => theme}/navigation.header.css (100%) diff --git a/css/navigation.header.css b/css/theme/navigation.header.css similarity index 100% rename from css/navigation.header.css rename to css/theme/navigation.header.css -- GitLab From 2582170db2e215756ab75df7fd54fba9f18bfc0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20BRINDLE?= <sebastien.brindle@kleegroup.com> Date: Mon, 27 Jan 2025 11:50:42 +0100 Subject: [PATCH 13/45] Finalize form table and tabledrag rendering. --- css/component/form.css | 15 +++++++++++++++ css/component/tabledrag.css | 9 +++++++++ js/tabledrag.js | 18 ++++++++++++++++++ 3 files changed, 42 insertions(+) diff --git a/css/component/form.css b/css/component/form.css index 5f0ee52..9a116e2 100644 --- a/css/component/form.css +++ b/css/component/form.css @@ -39,6 +39,21 @@ main[role="main"] > .fr-container--fluid > form { margin-block: var(--form-item-spacing); } +/* Disable table scrolling into form item - START */ +.form-item .fr-table__container { + overflow: initial; +} + +.form-item .fr-table > table caption { + max-width: calc(100vw - 2rem); +} + +.form-item .fr-table .fr-table__wrapper .fr-table__content table th, +.form-item .fr-table .fr-table__wrapper .fr-table__content table td { + white-space: normal; +} +/* Disable table scrolling into form - END */ + .views-exposed-form { display: flex; flex-wrap: wrap; diff --git a/css/component/tabledrag.css b/css/component/tabledrag.css index 90a7957..76f5569 100644 --- a/css/component/tabledrag.css +++ b/css/component/tabledrag.css @@ -20,6 +20,11 @@ a.tabledrag-handle .handle { background-position: center center; } +.draggable.drag td, +.draggable.drag th { + background-color: var(--background-default-grey-active); +} + .field-multiple-drag { max-width: fit-content; } @@ -31,3 +36,7 @@ a.tabledrag-handle .handle { .tabledrag-toggle-weight-wrapper + .fr-table { margin-top: 0; } + +.tabledrag-changed-warning { + color: var(--warning-425-625); +} diff --git a/js/tabledrag.js b/js/tabledrag.js index 8d38816..c44218f 100644 --- a/js/tabledrag.js +++ b/js/tabledrag.js @@ -20,6 +20,24 @@ ); const initColumnsOriginal = Drupal.tableDrag.prototype.initColumns; + const addChangedWarningOriginal = Drupal.tableDrag.prototype.row.prototype.addChangedWarning; + + /** + @inheritDoc + */ + Drupal.tableDrag.prototype.row.prototype.addChangedWarning = function () { + const $table = $(this.table.parentNode); + + // Do not add the changed warning if one is already present. + if (!$table.find('.tabledrag-changed-warning').length) { + addChangedWarningOriginal.call(this); + + const $tableWrapper = $table.parents(".fr-table"); + + // Move changed warning message before DSFR table wrapper. + $tableWrapper.parent().prepend($table.find('.tabledrag-changed-warning')); + } + }; /** * @inheritDoc -- GitLab From b87d063eb159d73b764cc4a47d2003b71dc15efd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20BRINDLE?= <sebastien.brindle@kleegroup.com> Date: Mon, 27 Jan 2025 17:16:57 +0100 Subject: [PATCH 14/45] Manage form groups styles. --- components/accordion/accordion.component.yml | 3 + components/accordion/accordion.twig | 11 ++- css/component/form.css | 13 ++- css/component/vertical-tabs.css | 16 ---- css/theme/horizontal-tabs.css | 61 +++++++++++++ css/theme/media-library.css | 2 +- css/theme/vertical-tabs.css | 49 +++++++++++ dsfr4drupal.info.yml | 2 + dsfr4drupal.libraries.yml | 17 +++- dsfr4drupal.theme | 14 +++ includes/form.theme | 21 +++++ js/accordion.js | 22 +++++ js/horizontal-tabs.js | 55 ++++++++++++ js/tabledrag.js | 3 +- src/Dsfr4DrupalPreRender.php | 87 +++++++++++++++++++ .../form/details--horizontal-tabs.html.twig | 2 + .../form/details--vertical-tabs.html.twig | 2 + templates/form/horizontal-tabs.html.twig | 6 ++ templates/system/details.html.twig | 16 ++++ 19 files changed, 376 insertions(+), 26 deletions(-) delete mode 100644 css/component/vertical-tabs.css create mode 100644 css/theme/horizontal-tabs.css create mode 100644 css/theme/vertical-tabs.css create mode 100644 js/accordion.js create mode 100644 js/horizontal-tabs.js create mode 100644 src/Dsfr4DrupalPreRender.php create mode 100644 templates/form/details--horizontal-tabs.html.twig create mode 100644 templates/form/details--vertical-tabs.html.twig create mode 100644 templates/form/horizontal-tabs.html.twig create mode 100644 templates/system/details.html.twig diff --git a/components/accordion/accordion.component.yml b/components/accordion/accordion.component.yml index 5c1a7a2..9b05443 100644 --- a/components/accordion/accordion.component.yml +++ b/components/accordion/accordion.component.yml @@ -10,6 +10,9 @@ props: attributes: type: Drupal\Core\Template\Attribute title: Attributes + button_attributes: + type: Drupal\Core\Template\Attribute + title: Button attributes content: type: string title: Content diff --git a/components/accordion/accordion.twig b/components/accordion/accordion.twig index 1b4df84..ec6060f 100644 --- a/components/accordion/accordion.twig +++ b/components/accordion/accordion.twig @@ -1,13 +1,16 @@ {% set accordion_id = accordion_id|default('accordion-' ~ random()) %} {% set attributes = attributes|default(create_attribute()) %} +{% set button_attributes = button_attributes|default(create_attribute()) %} {% set title_tag = title_tag|default('h3') %} <section{{ attributes.addClass('fr-accordion') }}> <{{ title_tag }} class="fr-accordion__title"> - <button class="fr-accordion__btn" - aria-expanded="{{ expanded ? 'true' : 'false' }}" - aria-controls="{{ accordion_id }}" - type="button"> + <button{{ button_attributes + .addClass('fr-accordion__btn') + .setAttribute('type', button) + .setAttribute('aria-expanded', expanded ? 'true' : 'false') + .setAttribute('aria-controls', accordion_id) }} + > {{ title }} </button> </{{ title_tag }}> diff --git a/css/component/form.css b/css/component/form.css index 9a116e2..a61b3b6 100644 --- a/css/component/form.css +++ b/css/component/form.css @@ -58,12 +58,12 @@ main[role="main"] > .fr-container--fluid > form { display: flex; flex-wrap: wrap; margin-block: var(--form-spacing); - padding: var(--form-spacing-xs) var(--form-spacing) var(--form-spacing) var(--form-spacing); + padding: var(--form-spacing-xs) var(--form-spacing) var(--form-spacing); border: 1px solid var(--border-default-grey); } .fr-upload-group { - padding: var(--form-spacing-xs) var(--form-spacing) var(--form-spacing) var(--form-spacing); + padding: var(--form-spacing-xs) var(--form-spacing) var(--form-spacing); border: 1px solid var(--border-default-grey); } @@ -79,3 +79,12 @@ main[role="main"] > .fr-container--fluid > form { padding-right: 2.5rem; background-position: calc(100% - 1rem) 50%; } + +.paragraphs-tabs-wrapper { + padding: var(--form-spacing-xs) var(--form-spacing) var(--form-spacing); + border: 1px solid var(--border-default-grey); +} + +.paragraphs-tabs-wrapper > .form-item { + margin-block: 0; +} diff --git a/css/component/vertical-tabs.css b/css/component/vertical-tabs.css deleted file mode 100644 index b2decff..0000000 --- a/css/component/vertical-tabs.css +++ /dev/null @@ -1,16 +0,0 @@ -/** - * @file - * Manage styles for vertical tabs. - */ - -.vertical-tabs__menu [href] { - background-image: none; -} - -.vertical-tabs__pane { - padding: 1.5rem; -} - -.vertical-tabs__pane .fr-input { - box-sizing: border-box; -} diff --git a/css/theme/horizontal-tabs.css b/css/theme/horizontal-tabs.css new file mode 100644 index 0000000..489e76e --- /dev/null +++ b/css/theme/horizontal-tabs.css @@ -0,0 +1,61 @@ +/** + * @file + * Manage styles for horizontal tabs. + * Try as much as possible to reproduce the styles of the DSFR tabs. + */ + +.horizontal-tabs { + margin: 0 0 var(--form-spacing); + border: 0; +} + +.horizontal-tabs .horizontal-tabs-list { + /* Remove uwanted styles */ + background: none; + border: 0; + + /* Force DSFR tabs styles */ + display: flex; + margin: -4px 0; + padding: 4px .75rem; +} + +.horizontal-tabs ul.horizontal-tabs-list li a, +.horizontal-tabs ul.horizontal-tabs-list li.selected a { + /* Force DSFR tabs styles */ + display: inline-flex; + padding: .5rem 1rem; + position: relative; +} + +.horizontal-tabs .horizontal-tab-button { + /* Remove uwanted styles */ + float: none; + background: none; + border: 0; + min-width: auto; +} + +.horizontal-tabs .horizontal-tab-button a:hover { + /* Force DSFR tabs styles */ + background-color: var(--hover-tint); +} + +.horizontal-tabs-list.fr-tabs__list + .horizontal-tabs-panes { + /* Fix order */ + order: 3; +} + +.horizontal-tabs-panes { + border: 1px solid var(--border-default-grey); + border-top: 0; + padding: var(--form-spacing-xs) var(--form-spacing) var(--form-spacing-s); +} + +.horizontal-tabs-panes .horizontal-tabs-pane { + padding: 0; +} + +.horizontal-tabs-panes .fr-input { + box-sizing: border-box; +} diff --git a/css/theme/media-library.css b/css/theme/media-library.css index 08498a8..e1bddc1 100644 --- a/css/theme/media-library.css +++ b/css/theme/media-library.css @@ -21,7 +21,7 @@ } .js-media-library-add-form-added-media li { - padding: var(--form-spacing-xs) var(--form-spacing) var(--form-spacing) var(--form-spacing); + padding: var(--form-spacing-xs) var(--form-spacing) var(--form-spacing); border: 1px solid var(--border-default-grey); } diff --git a/css/theme/vertical-tabs.css b/css/theme/vertical-tabs.css new file mode 100644 index 0000000..fb663b6 --- /dev/null +++ b/css/theme/vertical-tabs.css @@ -0,0 +1,49 @@ +/** + * @file + * Manage styles for vertical tabs. + */ + +.vertical-tabs__menu [href] { + background-image: none; +} + +.vertical-tabs__menu li { + --li-bottom: 0; +} + +.vertical-tabs__menu-item a:hover { + outline: none; +} + +.vertical-tabs__menu-item:not(.is-selected) a { + background-color: var(--background-action-low-blue-france); +} + +.vertical-tabs__menu-item:not(.is-selected) a:hover { + background-color: var(--background-action-low-blue-france-hover); +} + +.vertical-tabs__menu-item.is-selected a { + background-color: var(--background-default-grey); + background-image: linear-gradient(0deg,var(--border-active-blue-france),var(--border-active-blue-france)),linear-gradient(0deg,var(--border-default-grey),var(--border-default-grey)),linear-gradient(0deg,var(--border-default-grey),var(--border-default-grey)),linear-gradient(0deg,var(--border-default-grey),var(--border-default-grey)); + background-size: 100% 2px,1px calc(100% - 1px),1px calc(100% - 1px),0 1px; + color: var(--text-active-blue-france); +} + +.vertical-tabs__menu-item.is-selected .vertical-tabs__menu-item-title { + color: inherit; +} + +.vertical-tabs__menu-item a:focus .vertical-tabs__menu-item-title, +.vertical-tabs__menu-item a:active .vertical-tabs__menu-item-title, +.vertical-tabs__menu-item a:hover .vertical-tabs__menu-item-title { + text-decoration: none; +} + +.vertical-tabs__panes { + padding: var(--form-spacing-xs) var(--form-spacing) var(--form-spacing-s); +} + +.vertical-tabs__pane .fr-input { + box-sizing: border-box; +} diff --git a/dsfr4drupal.info.yml b/dsfr4drupal.info.yml index 8d38db5..fea536a 100644 --- a/dsfr4drupal.info.yml +++ b/dsfr4drupal.info.yml @@ -46,6 +46,8 @@ libraries-extend: - dsfr4drupal/drupal.tabledrag core/drupal.vertical-tabs: - dsfr4drupal/drupal.vertical-tabs + field_group/element.horizontal_tabs: + - dsfr4drupal/element.horizontal_tabs media_library/view: - dsfr4drupal/media_library.theme media_library/widget: diff --git a/dsfr4drupal.libraries.yml b/dsfr4drupal.libraries.yml index 7509194..befee60 100644 --- a/dsfr4drupal.libraries.yml +++ b/dsfr4drupal.libraries.yml @@ -6,7 +6,10 @@ component.accordion: css: component: /libraries/dsfr/dist/component/accordion/accordion.min.css: { minified: true } + js: + js/accordion.js: {} dependencies: + - core/once - dsfr4drupal/core component.alert: @@ -577,8 +580,18 @@ drupal.tabledrag: drupal.vertical-tabs: css: - component: - css/component/vertical-tabs.css: {} + theme: + css/theme/vertical-tabs.css: {} + +element.horizontal_tabs: + css: + theme: + css/theme/horizontal-tabs.css: {} + js: + js/horizontal-tabs.js: {} + dependencies: + - dsfr4drupal/component.tab + - core/once #legacy: # js: diff --git a/dsfr4drupal.theme b/dsfr4drupal.theme index 657df94..f2d9a31 100644 --- a/dsfr4drupal.theme +++ b/dsfr4drupal.theme @@ -10,6 +10,7 @@ declare(strict_types=1); use Drupal\Core\Asset\AttachedAssetsInterface; use Drupal\Core\Language\LanguageInterface; use Drupal\dsfr4drupal\Dsfr4DrupalInterface; +use Drupal\dsfr4drupal\Dsfr4DrupalPreRender; // Include all files from the includes directory. $includes_path = dirname(__FILE__) . '/includes/*.theme'; @@ -17,6 +18,19 @@ foreach (glob($includes_path) as $file) { require_once dirname(__FILE__) . '/includes/' . basename($file); } +/** + * Implements hook_element_info_alter(). + */ +function dsfr4drupal_element_info_alter(array &$type): void { + if (isset($type['horizontal_tabs'])) { + $type['horizontal_tabs']['#pre_render'][] = [Dsfr4DrupalPreRender::class, 'horizontalTabs']; + } + if (isset($type['vertical_tabs'])) { + $type['vertical_tabs']['#pre_render'][] = [Dsfr4DrupalPreRender::class, 'verticalTabs']; + } + +} + /** * Implements hook_preprocess(). */ diff --git a/includes/form.theme b/includes/form.theme index d639458..02e5c9c 100644 --- a/includes/form.theme +++ b/includes/form.theme @@ -277,6 +277,27 @@ function dsfr4drupal_preprocess_textarea(array &$variables): void { } } +/** + * Implements hook_theme_suggestions_HOOK_alter() for details. + */ +function dsfr4drupal_theme_suggestions_details_alter(array &$suggestions, array $variables): void { + /** + * The property "#horizontal_tab_item" is added during element prerender. + * @see: \Drupal\dsfr4drupal\Dsfr4DrupalPrerender::horizontalTabs() + */ + if (!empty($variables['element']['#horizontal_tab_item'])) { + $suggestions[] = 'details__horizontal_tabs'; + } + + /** + * The property "#vertical_tab_item" is added during element prerender. + * @see: \Drupal\dsfr4drupal\Dsfr4DrupalPrerender::verticalTabs() + */ + if (!empty($variables['element']['#vertical_tab_item'])) { + $suggestions[] = 'details__vertical_tabs'; + } +} + /** * Implements hook_theme_suggestions_HOOK_alter() for "form_elemtn". */ diff --git a/js/accordion.js b/js/accordion.js new file mode 100644 index 0000000..14679c1 --- /dev/null +++ b/js/accordion.js @@ -0,0 +1,22 @@ +/** + * @file + * Manage accordion features with DSFR. + */ + +((Drupal, once) => { + + Drupal.behaviors.dsfrAccordion = { + attach: (context) => { + + // Search all accordion in current context. + once("dsfr-accordion", ".fr-accordion", context).forEach((element) => { + // Manage accordion button click event. + element.querySelector(".fr-accordion__btn").addEventListener("click", event => { + // Disable form submission. + event.preventDefault(); + }); + }); + } + }; + +})(Drupal, once); diff --git a/js/horizontal-tabs.js b/js/horizontal-tabs.js new file mode 100644 index 0000000..cb1604a --- /dev/null +++ b/js/horizontal-tabs.js @@ -0,0 +1,55 @@ +/** + * @file + * Manage horizontal tabs features with DSFR. + */ + +((Drupal, once) => { + + const CLASS_LIST = "fr-tabs__list"; + const CLASS_TAB = "fr-tabs__tab"; + const CLASS_TAB_SELECTED = "fr-tabs__tab--selected"; + const CLASS_TABS = "fr-tabs"; + + Drupal.behaviors.dsfrHorizontalTabs = { + attach: (context) => { + + // Search all horizontal tabs in current context. + once("dsfr-horizontal-tabs", ".horizontal-tabs", context).forEach((element) => { + const wrapper = element.querySelector(`.${CLASS_TABS}`); + if (!wrapper) { + return; + } + + const list = element.querySelector(`.horizontal-tabs-list.${CLASS_LIST}`); + if (!list) { + return; + } + + wrapper.append(list); + + list.querySelectorAll("li").forEach((item) => { + + const link = item.querySelector("a"); + link.classList.add(CLASS_TAB); + + if (item.classList.contains("selected")) { + link.classList.add(CLASS_TAB_SELECTED); + link.setAttribute("aria-selected", "true"); + } + + link.addEventListener("click", () => { + // Remove selected class on old active link. + const selected = list.querySelector(`.${CLASS_TAB_SELECTED}`); + selected.classList.remove(CLASS_TAB_SELECTED); + selected.removeAttribute("aria-selected"); + + // Add class to new element. + link.classList.add(CLASS_TAB_SELECTED); + link.setAttribute("aria-selected", "true"); + }) + }); + }); + } + }; + +})(Drupal, once); diff --git a/js/tabledrag.js b/js/tabledrag.js index c44218f..da730da 100644 --- a/js/tabledrag.js +++ b/js/tabledrag.js @@ -1,6 +1,6 @@ /** * @file - * Provide dragging capabilities to admin uis. + * Manage tabbledrag features with DSFR. */ /** @@ -10,6 +10,7 @@ */ (function ($, Drupal, drupalSettings) { + /** * Store the state of weight columns display for all tables. * diff --git a/src/Dsfr4DrupalPreRender.php b/src/Dsfr4DrupalPreRender.php new file mode 100644 index 0000000..0674233 --- /dev/null +++ b/src/Dsfr4DrupalPreRender.php @@ -0,0 +1,87 @@ +<?php + +namespace Drupal\dsfr4drupal; + +use Drupal\Core\Render\Element; +use Drupal\Core\Security\TrustedCallbackInterface; + +/** + * Implements trusted prerender callbacks for the "DSFR for Drupal" theme. + * + * @internal + */ +class Dsfr4DrupalPreRender implements TrustedCallbackInterface { + + /** + * Prerender callback for Horizontal Tabs element. + * + * @param array $element + * The render array element. + * + * @return array + * The new render array element. + */ + public static function horizontalTabs(array $element): array { + return self::tabs($element, 'horizontal'); + } + + /** + * Prerender callback for Vertical Tabs element. + * + * @param array $element + * The render array element. + * + * @return array + * The new render array element. + */ + public static function verticalTabs(array $element): array { + return self::tabs($element, 'vertical'); + } + + /** + * Prerender callback for tabs element. + * + * @param array $element + * The render array element. + * + * @return array + * The new render array element. + */ + private static function tabs(array $element, string $orientation): array { + $isDetails = isset($element['group']['#type']) && $element['group']['#type'] === 'details'; + $existingGroups = isset($element['group']['#groups']) && is_array($element['group']['#groups']); + + // If the horizontal/vertical tabs have a details group, add attributes to those + // details elements so they are styled as accordion items and have BEM classes. + if ($isDetails && $existingGroups) { + $groupKeys = Element::children($element['group']['#groups'], TRUE); + + $groupKey = implode('][', $element['#parents']); + // Only check siblings against groups because we are only looking for + // group elements. + if (in_array($groupKey, $groupKeys)) { + $childrenKeys = Element::children($element['group']['#groups'][$groupKey], TRUE); + + foreach ($childrenKeys as $childKey) { + $type = $element['group']['#groups'][$groupKey][$childKey]['#type'] ?? NULL; + if ($type === 'details') { + $element['group']['#groups'][$groupKey][$childKey]['#' . $orientation . '_tab_item'] = TRUE; + } + } + } + } + + return $element; + } + + /** + * {@inheritdoc} + */ + public static function trustedCallbacks(): array { + return [ + 'horizontalTabs', + 'verticalTabs', + ]; + } + +} diff --git a/templates/form/details--horizontal-tabs.html.twig b/templates/form/details--horizontal-tabs.html.twig new file mode 100644 index 0000000..c5a902d --- /dev/null +++ b/templates/form/details--horizontal-tabs.html.twig @@ -0,0 +1,2 @@ +{# Specific "details" templating for vertical tabs. Do not use DSFR accordion. #} +{% include '@system/templates/details.html.twig' %} diff --git a/templates/form/details--vertical-tabs.html.twig b/templates/form/details--vertical-tabs.html.twig new file mode 100644 index 0000000..c5a902d --- /dev/null +++ b/templates/form/details--vertical-tabs.html.twig @@ -0,0 +1,2 @@ +{# Specific "details" templating for vertical tabs. Do not use DSFR accordion. #} +{% include '@system/templates/details.html.twig' %} diff --git a/templates/form/horizontal-tabs.html.twig b/templates/form/horizontal-tabs.html.twig new file mode 100644 index 0000000..7af90c4 --- /dev/null +++ b/templates/form/horizontal-tabs.html.twig @@ -0,0 +1,6 @@ +<div data-horizontal-tabs class="horizontal-tabs"> + <div class="fr-tabs"></div> + {# Cannot add list into "fr-tabs", otherwise it's not populated. #} + <ul data-horizontal-tabs-list class="horizontal-tabs-list fr-tabs__list"></ul> + <div data-horizontal-tabs-panes{{ attributes }}>{{ children }}</div> +</div> diff --git a/templates/system/details.html.twig b/templates/system/details.html.twig new file mode 100644 index 0000000..991c919 --- /dev/null +++ b/templates/system/details.html.twig @@ -0,0 +1,16 @@ +{% set content %} + {% if errors %} + <div> + {{ errors }} + </div> + {% endif %} + + {{ description }} + {{ children }} + {{ value }} +{% endset %} + +{{ include('dsfr4drupal:accordion', { + 'button_attributes': create_attribute({'class': required ? ['js-form-required', 'form-required'] : ''}), + 'title_tag': 'div', +}) }} -- GitLab From 8dfe543f54096dd9fdc919bf4cba25b70f7b3602 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20BRINDLE?= <sebastien.brindle@kleegroup.com> Date: Mon, 27 Jan 2025 18:26:58 +0100 Subject: [PATCH 15/45] Add actions button margin. --- css/component/form.css | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/css/component/form.css b/css/component/form.css index a61b3b6..ceef11d 100644 --- a/css/component/form.css +++ b/css/component/form.css @@ -39,6 +39,12 @@ main[role="main"] > .fr-container--fluid > form { margin-block: var(--form-item-spacing); } +.form-actions a + button, +.form-actions button + button, +.form-actions button + a { + margin-left: var(--form-item-spacing); +} + /* Disable table scrolling into form item - START */ .form-item .fr-table__container { overflow: initial; -- GitLab From d8e2cd51b8a379391b0b0cc6386526913c3e91d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20BRINDLE?= <sebastien.brindle@kleegroup.com> Date: Mon, 27 Jan 2025 18:29:44 +0100 Subject: [PATCH 16/45] Remove too margins. --- css/component/form.css | 2 -- 1 file changed, 2 deletions(-) diff --git a/css/component/form.css b/css/component/form.css index ceef11d..f612baf 100644 --- a/css/component/form.css +++ b/css/component/form.css @@ -33,8 +33,6 @@ main[role="main"] > .fr-container--fluid > form { margin-block: var(--form-spacing); } -.form-actions, -.form-item, .form-wrapper { margin-block: var(--form-item-spacing); } -- GitLab From 7d0bbfbd7c7ca178012fb66d81f52fd60fb414d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20BRINDLE?= <sebastien.brindle@kleegroup.com> Date: Wed, 29 Jan 2025 18:02:32 +0100 Subject: [PATCH 17/45] Prepare to manage paragraphs widgets. --- css/component/paragraphs-admin.css | 29 +++++++++++++++++++++ css/component/paragraphs-widget.css | 39 +++++++++++++++++++++++++++++ css/theme/horizontal-tabs.css | 3 ++- css/theme/vertical-tabs.css | 3 ++- dsfr4drupal.info.yml | 4 +++ dsfr4drupal.libraries.yml | 10 ++++++++ 6 files changed, 86 insertions(+), 2 deletions(-) create mode 100644 css/component/paragraphs-admin.css create mode 100644 css/component/paragraphs-widget.css diff --git a/css/component/paragraphs-admin.css b/css/component/paragraphs-admin.css new file mode 100644 index 0000000..bcf024f --- /dev/null +++ b/css/component/paragraphs-admin.css @@ -0,0 +1,29 @@ +/** + * @file + * Manage styles for paragraphs admin. + */ + +.paragraph-type-title { + font-size: 1.5em; + font-weight: 700; + margin-top: var(--form-spacing-xs); +} + +.js .field--widget-entity-reference-paragraphs td { + /* Force DSFR table styles */ + padding: .5rem 1rem; +} + +.js .field--widget-entity-reference-paragraphs .field-multiple-table > thead > tr > th:nth-child(2), +.js .field--widget-entity-reference-paragraphs .field-multiple-table > tbody > tr > td:nth-child(3) { + padding: 0; +} + +.js .field--widget-entity-reference-paragraphs .field-multiple-drag { + vertical-align: middle; + padding-right: 0; +} + +.paragraphs-tabs-wrapper .field-multiple-table > thead > tr .field-label .paragraphs-actions { + margin-inline: auto 0; +} diff --git a/css/component/paragraphs-widget.css b/css/component/paragraphs-widget.css new file mode 100644 index 0000000..6c5f521 --- /dev/null +++ b/css/component/paragraphs-widget.css @@ -0,0 +1,39 @@ +/** + * @file + * Manage styles for paragraphs widget. + */ + +.paragraph-type-label { + font-size: 1.5em; + font-weight: 700; +} + +.js .field--widget-paragraphs td { + /* Force DSFR table styles */ + padding: .5rem 1rem; +} + +.js .field--widget-paragraphs .field-multiple-drag { + min-width: auto; +} + +.js .field--widget-paragraphs .draggable .tabledrag-handle { + padding: 0; +} + +.js .field--widget-paragraphs .draggable .field-multiple-drag { + padding-right: 0; +} + +.paragraphs-tabs-wrapper .field-multiple-table > thead > tr > th:nth-child(2), +.paragraphs-tabs-wrapper .field-multiple-table > tbody > tr > td:nth-child(3) { + padding: 0; +} + +.paragraphs-tabs-wrapper .field-multiple-table > thead > tr .field-label .label { + display: inline-block; +} + +.paragraphs-tabs-wrapper .field-multiple-table > thead > tr .field-label .paragraphs-actions { + margin-inline: auto 0; +} diff --git a/css/theme/horizontal-tabs.css b/css/theme/horizontal-tabs.css index 489e76e..c761c2c 100644 --- a/css/theme/horizontal-tabs.css +++ b/css/theme/horizontal-tabs.css @@ -56,6 +56,7 @@ padding: 0; } -.horizontal-tabs-panes .fr-input { +.horizontal-tabs-panes .fr-input, +.horizontal-tabs-panes .fr-select { box-sizing: border-box; } diff --git a/css/theme/vertical-tabs.css b/css/theme/vertical-tabs.css index fb663b6..43ae05c 100644 --- a/css/theme/vertical-tabs.css +++ b/css/theme/vertical-tabs.css @@ -44,6 +44,7 @@ padding: var(--form-spacing-xs) var(--form-spacing) var(--form-spacing-s); } -.vertical-tabs__pane .fr-input { +.vertical-tabs__pane .fr-input, +.vertical-tabs__pane .fr-select { box-sizing: border-box; } diff --git a/dsfr4drupal.info.yml b/dsfr4drupal.info.yml index fea536a..3be50c0 100644 --- a/dsfr4drupal.info.yml +++ b/dsfr4drupal.info.yml @@ -56,6 +56,10 @@ libraries-extend: - dsfr4drupal/navigation.layout node/drupal.node.preview: - dsfr4drupal/drupal.node.preview + paragraphs/drupal.paragraphs.admin: + - dsfr4drupal/drupal.paragraphs.admin + paragraphs/drupal.paragraphs.widget: + - dsfr4drupal/drupal.paragraphs.widget tarte_au_citron/tarte_au_citron_lib: - dsfr4drupal/tarteaucitron tacjs/tarteaucitron.js: diff --git a/dsfr4drupal.libraries.yml b/dsfr4drupal.libraries.yml index befee60..da75f1f 100644 --- a/dsfr4drupal.libraries.yml +++ b/dsfr4drupal.libraries.yml @@ -571,6 +571,16 @@ drupal.node.preview: component: css/component/node-preview.css: {} +drupal.paragraphs.admin: + css: + component: + css/component/paragraphs-admin.css: { } + +drupal.paragraphs.widget: + css: + component: + css/component/paragraphs-widget.css: { } + drupal.tabledrag: css: component: -- GitLab From a215b73f1ea004ce256704a7c1ef63a3df8a428e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20BRINDLE?= <sebastien.brindle@kleegroup.com> Date: Thu, 30 Jan 2025 11:21:07 +0100 Subject: [PATCH 18/45] Finalize paragraphs widgets styling. --- ...agraphs-admin.css => paragraphs.admin.css} | 0 ...raphs-widget.css => paragraphs.widget.css} | 6 ++ css/theme/horizontal-tabs.css | 4 +- dsfr4drupal.libraries.yml | 12 +++- js/paragraphs.widget.js | 58 +++++++++++++++++++ 5 files changed, 75 insertions(+), 5 deletions(-) rename css/component/{paragraphs-admin.css => paragraphs.admin.css} (100%) rename css/component/{paragraphs-widget.css => paragraphs.widget.css} (86%) create mode 100644 js/paragraphs.widget.js diff --git a/css/component/paragraphs-admin.css b/css/component/paragraphs.admin.css similarity index 100% rename from css/component/paragraphs-admin.css rename to css/component/paragraphs.admin.css diff --git a/css/component/paragraphs-widget.css b/css/component/paragraphs.widget.css similarity index 86% rename from css/component/paragraphs-widget.css rename to css/component/paragraphs.widget.css index 6c5f521..2c29999 100644 --- a/css/component/paragraphs-widget.css +++ b/css/component/paragraphs.widget.css @@ -37,3 +37,9 @@ .paragraphs-tabs-wrapper .field-multiple-table > thead > tr .field-label .paragraphs-actions { margin-inline: auto 0; } + +.is-horizontal .paragraphs-tabs:first-of-type { + /* Remove unwanted styles */ + position: static; + background-color: transparent; +} diff --git a/css/theme/horizontal-tabs.css b/css/theme/horizontal-tabs.css index c761c2c..f0d6610 100644 --- a/css/theme/horizontal-tabs.css +++ b/css/theme/horizontal-tabs.css @@ -10,7 +10,7 @@ } .horizontal-tabs .horizontal-tabs-list { - /* Remove uwanted styles */ + /* Remove unwanted styles */ background: none; border: 0; @@ -29,7 +29,7 @@ } .horizontal-tabs .horizontal-tab-button { - /* Remove uwanted styles */ + /* Remove unwanted styles */ float: none; background: none; border: 0; diff --git a/dsfr4drupal.libraries.yml b/dsfr4drupal.libraries.yml index da75f1f..5f003e4 100644 --- a/dsfr4drupal.libraries.yml +++ b/dsfr4drupal.libraries.yml @@ -574,12 +574,18 @@ drupal.node.preview: drupal.paragraphs.admin: css: component: - css/component/paragraphs-admin.css: { } + css/component/paragraphs.admin.css: { } drupal.paragraphs.widget: css: component: - css/component/paragraphs-widget.css: { } + css/component/paragraphs.widget.css: { } + js: + js/paragraphs.widget.js: {} + dependencies: + - core/drupal + - core/once + - dsfr4drupal/component.tab drupal.tabledrag: css: @@ -600,8 +606,8 @@ element.horizontal_tabs: js: js/horizontal-tabs.js: {} dependencies: - - dsfr4drupal/component.tab - core/once + - dsfr4drupal/component.tab #legacy: # js: diff --git a/js/paragraphs.widget.js b/js/paragraphs.widget.js new file mode 100644 index 0000000..bd349eb --- /dev/null +++ b/js/paragraphs.widget.js @@ -0,0 +1,58 @@ + + +(function (Drupal, once) { + "use strict"; + + const CLASS_LIST = "fr-tabs__list"; + const CLASS_TAB = "fr-tabs__tab"; + const CLASS_TAB_SELECTED = "fr-tabs__tab--selected"; + const CLASS_TABS = "fr-tabs"; + + Drupal.behaviors.dsfrParagraphsWidget = { + attach: (context) => { + const wrappers = once("dsfr-paragraphs-tabs-wrapper", ".paragraphs-tabs-wrapper", context); + if (!wrappers) { + return; + } + + wrappers.forEach(wrapper => { + const list = wrapper.querySelector(".paragraphs-tabs"); + if ( + !list || + list.classList.contains("paragraphs-tabs-hide") + ) { + return; + } + + const listWrapper = document.createElement("div"); + listWrapper.classList.add(CLASS_TABS); + wrapper.prepend(listWrapper); + + listWrapper.appendChild(list); + list.classList.add(CLASS_LIST); + + list.querySelectorAll("li > a").forEach(link => { + link.classList.add(CLASS_TAB); + + if (link.classList.contains("is-active")) { + link.classList.add(CLASS_TAB_SELECTED); + link.setAttribute("aria-selected", "true"); + } + + link.addEventListener("click", () => { + // Remove selected class on old active link. + const selected = list.querySelector(`.${CLASS_TAB_SELECTED}`); + selected.classList.remove(CLASS_TAB_SELECTED); + selected.removeAttribute("aria-selected"); + + // Add class to new element. + link.classList.add(CLASS_TAB_SELECTED); + link.setAttribute("aria-selected", "true"); + }) + }); + }); + + }, + }; + +})(Drupal, once); -- GitLab From a118f5fae5d66bdb519defca0385bec6e806bb1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20BRINDLE?= <sebastien.brindle@kleegroup.com> Date: Fri, 31 Jan 2025 16:24:01 +0100 Subject: [PATCH 19/45] Remove EOL. --- dsfr4drupal.theme | 1 - 1 file changed, 1 deletion(-) diff --git a/dsfr4drupal.theme b/dsfr4drupal.theme index f2d9a31..0a99994 100644 --- a/dsfr4drupal.theme +++ b/dsfr4drupal.theme @@ -28,7 +28,6 @@ function dsfr4drupal_element_info_alter(array &$type): void { if (isset($type['vertical_tabs'])) { $type['vertical_tabs']['#pre_render'][] = [Dsfr4DrupalPreRender::class, 'verticalTabs']; } - } /** -- GitLab From 96ca747015de02350a4e10fe04c1bc255cd90588 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20BRINDLE?= <sebastien.brindle@kleegroup.com> Date: Wed, 5 Feb 2025 11:20:32 +0100 Subject: [PATCH 20/45] Prepare to stylize Layout builder page. --- css/theme/layout-builder.css | 43 ++++++++++++++++++++++++++++++++++++ dsfr4drupal.info.yml | 2 ++ dsfr4drupal.libraries.yml | 5 +++++ 3 files changed, 50 insertions(+) create mode 100644 css/theme/layout-builder.css diff --git a/css/theme/layout-builder.css b/css/theme/layout-builder.css new file mode 100644 index 0000000..1f1274f --- /dev/null +++ b/css/theme/layout-builder.css @@ -0,0 +1,43 @@ +/** + * @file + * Manage styles for layout builder. + */ + +.layout-builder__link--add { + color: var(--text-default-grey); + padding-left: 0; +} + +.layout-builder__link--add[href]:hover { + --underline-hover-width: 0; +} + +.layout-builder__link--add::before { + content: ''; + display: inline-block; + vertical-align: middle; + width: 16px; + height: 16px; + background-color: var(--text-default-grey); + mask-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='16px' height='16px'%3e%3cpath d='M0.656,9.023c0,0.274,0.224,0.5,0.499,0.5l4.853,0.001c0.274-0.001,0.501,0.226,0.5,0.5l0.001,4.853 c-0.001,0.273,0.227,0.5,0.501,0.5l1.995-0.009c0.273-0.003,0.497-0.229,0.5-0.503l0.002-4.806c0-0.272,0.228-0.5,0.499-0.502 l4.831-0.021c0.271-0.005,0.497-0.23,0.501-0.502l0.008-1.998c0-0.276-0.225-0.5-0.499-0.5l-4.852,0c-0.275,0-0.502-0.228-0.501-0.5 L9.493,1.184c0-0.275-0.225-0.499-0.5-0.499L6.997,0.693C6.722,0.694,6.496,0.92,6.495,1.195L6.476,6.026 c-0.001,0.274-0.227,0.5-0.501,0.5L1.167,6.525C0.892,6.526,0.665,6.752,0.665,7.026L0.656,9.023z'/%3e%3c/svg%3e"); + mask-position: left center; + mask-repeat: no-repeat; + mask-size: 100%; + margin-right: .25rem; +} + +.layout-builder__link--add::before:hover { + background-color: var(--hover-tint); +} + +.layout-builder__link--remove { + /* Force layout builder styles. */ + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16'%3e%3cpath fill='%23bebebe' d='M3.51 13.925c.194.194.512.195.706.001l3.432-3.431c.194-.194.514-.194.708 0l3.432 3.431c.192.194.514.193.707-.001l1.405-1.417c.191-.195.189-.514-.002-.709l-3.397-3.4c-.192-.193-.192-.514-.002-.708l3.401-3.43c.189-.195.189-.515 0-.709l-1.407-1.418c-.195-.195-.513-.195-.707-.001l-3.43 3.431c-.195.194-.516.194-.708 0l-3.432-3.431c-.195-.195-.512-.194-.706.001l-1.407 1.417c-.194.195-.194.515 0 .71l3.403 3.429c.193.195.193.514-.001.708l-3.4 3.399c-.194.195-.195.516-.001.709l1.406 1.419z'/%3e%3c/svg%3e"); + background-position: center center; + background-repeat: no-repeat; + background-size: auto; +} + +.layout-builder__region { + margin-top: .25rem; +} diff --git a/dsfr4drupal.info.yml b/dsfr4drupal.info.yml index 3be50c0..ce69ee7 100644 --- a/dsfr4drupal.info.yml +++ b/dsfr4drupal.info.yml @@ -48,6 +48,8 @@ libraries-extend: - dsfr4drupal/drupal.vertical-tabs field_group/element.horizontal_tabs: - dsfr4drupal/element.horizontal_tabs + layout_builder/drupal.layout_builder: + - dsfr4drupal/drupal.layout_builder media_library/view: - dsfr4drupal/media_library.theme media_library/widget: diff --git a/dsfr4drupal.libraries.yml b/dsfr4drupal.libraries.yml index 5f003e4..a081e62 100644 --- a/dsfr4drupal.libraries.yml +++ b/dsfr4drupal.libraries.yml @@ -560,6 +560,11 @@ drupal.form: component: css/component/form.css: {} +drupal.layout_builder: + css: + theme: + css/theme/layout-builder.css: {} + drupal.message: js: js/messages.js: {} -- GitLab From bd3a51e6763c0dec16287b6c4f604a6994b6b9f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20BRINDLE?= <sebastien.brindle@kleegroup.com> Date: Thu, 6 Feb 2025 11:56:29 +0100 Subject: [PATCH 21/45] Finalize to stylize Layout builder page. --- includes/form.theme | 23 +---------- includes/system.theme | 38 +++++++++++++++++++ .../details--dialog-off-canvas.html.twig | 2 + 3 files changed, 41 insertions(+), 22 deletions(-) create mode 100644 includes/system.theme create mode 100644 templates/system/details--dialog-off-canvas.html.twig diff --git a/includes/form.theme b/includes/form.theme index 02e5c9c..74935ab 100644 --- a/includes/form.theme +++ b/includes/form.theme @@ -278,28 +278,7 @@ function dsfr4drupal_preprocess_textarea(array &$variables): void { } /** - * Implements hook_theme_suggestions_HOOK_alter() for details. - */ -function dsfr4drupal_theme_suggestions_details_alter(array &$suggestions, array $variables): void { - /** - * The property "#horizontal_tab_item" is added during element prerender. - * @see: \Drupal\dsfr4drupal\Dsfr4DrupalPrerender::horizontalTabs() - */ - if (!empty($variables['element']['#horizontal_tab_item'])) { - $suggestions[] = 'details__horizontal_tabs'; - } - - /** - * The property "#vertical_tab_item" is added during element prerender. - * @see: \Drupal\dsfr4drupal\Dsfr4DrupalPrerender::verticalTabs() - */ - if (!empty($variables['element']['#vertical_tab_item'])) { - $suggestions[] = 'details__vertical_tabs'; - } -} - -/** - * Implements hook_theme_suggestions_HOOK_alter() for "form_elemtn". + * Implements hook_theme_suggestions_HOOK_alter() for "form_element". */ function dsfr4drupal_theme_suggestions_form_element_alter(array &$suggestions, array $variables): void { if (empty($suggestions)) { diff --git a/includes/system.theme b/includes/system.theme new file mode 100644 index 0000000..49f3b39 --- /dev/null +++ b/includes/system.theme @@ -0,0 +1,38 @@ +<?php + +/** + * @file + * Functions to support system theming in the "DSFR for Drupal" theme. + */ + +declare(strict_types=1); + + +/** + * Implements hook_theme_suggestions_HOOK_alter() for details. + */ +function dsfr4drupal_theme_suggestions_details_alter(array &$suggestions, array $variables): void { + /** + * The property "#horizontal_tab_item" is added during element prerender. + * @see: \Drupal\dsfr4drupal\Dsfr4DrupalPrerender::horizontalTabs() + */ + if (!empty($variables['element']['#horizontal_tab_item'])) { + $suggestions[] = 'details__horizontal_tabs'; + } + + /** + * The property "#vertical_tab_item" is added during element prerender. + * @see: \Drupal\dsfr4drupal\Dsfr4DrupalPrerender::verticalTabs() + */ + if (!empty($variables['element']['#vertical_tab_item'])) { + $suggestions[] = 'details__vertical_tabs'; + } + + /** + * Styles are reset with Drupal dialog off canvas. + * Use default details rendering instead of DSFR accordion component. + */ + if (\Drupal::request()->query->get('_wrapper_format') === 'drupal_dialog.off_canvas') { + $suggestions[] = 'details__dialog_off_canvas'; + } +} diff --git a/templates/system/details--dialog-off-canvas.html.twig b/templates/system/details--dialog-off-canvas.html.twig new file mode 100644 index 0000000..9bbbd49 --- /dev/null +++ b/templates/system/details--dialog-off-canvas.html.twig @@ -0,0 +1,2 @@ +{# Styles are reset with Drupal dialog off canvas. Use default details rendering instead of DSFR accordion component. #} +{% include '@system/templates/details.html.twig' %} -- GitLab From 3e545f7b1c93c03ac632b6feaa3332a06095dc64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20BRINDLE?= <sebastien.brindle@kleegroup.com> Date: Thu, 6 Feb 2025 17:25:15 +0100 Subject: [PATCH 22/45] Remove double closing div. --- templates/form/input--password.html.twig | 1 - 1 file changed, 1 deletion(-) diff --git a/templates/form/input--password.html.twig b/templates/form/input--password.html.twig index 0fb51b5..93718b4 100644 --- a/templates/form/input--password.html.twig +++ b/templates/form/input--password.html.twig @@ -13,7 +13,6 @@ <label class="fr--password__checkbox fr-label" for="{{ show_password_id }}"> {{ show_password_label }} </label> - </div> </div> {% endif %} {{ attach_library('dsfr4drupal/component.password') }} -- GitLab From be52ebce3b52c99fe391bd87b4b2bc8bd5fed703 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20BRINDLE?= <sebastien.brindle@kleegroup.com> Date: Mon, 17 Feb 2025 15:57:46 +0100 Subject: [PATCH 23/45] Manage Select2 widget styles. --- css/theme/select2.css | 140 ++++++++++++++++++++++++++++++++++++++ dsfr4drupal.info.yml | 2 + dsfr4drupal.libraries.yml | 5 ++ 3 files changed, 147 insertions(+) create mode 100644 css/theme/select2.css diff --git a/css/theme/select2.css b/css/theme/select2.css new file mode 100644 index 0000000..24d8b4d --- /dev/null +++ b/css/theme/select2.css @@ -0,0 +1,140 @@ +/** + * @file + * Manage styles for Select2 widget. + */ + +/* Duplicate "select" component styles - START */ +.select2-container .selection { + display: block; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + width: 100%; + border-radius: 0.25rem 0.25rem 0 0; + font-size: 1rem; + line-height: 1.5rem; + padding: 0.5rem 2.5rem 0.5rem 1rem; + background-repeat: no-repeat; + background-position: calc(100% - 1rem) 50%; + background-size: 1rem 1rem; + color: var(--text-default-grey); + background-color: var(--background-contrast-grey); + + --idle: transparent; + --hover: var(--background-contrast-grey-hover); + --active: var(--background-contrast-grey-active); + box-shadow: inset 0 -2px 0 0 var(--border-plain-grey); + + --data-uri-svg: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' x='0px' y='0px' viewBox='0 0 24 24' ><path fill='%23161616' d='M12,13.1l5-4.9l1.4,1.4L12,15.9L5.6,9.5l1.4-1.4L12,13.1z'/></svg>"); + background-image: var(--data-uri-svg); +} + +:root[data-fr-theme=dark] .select2-container { + --data-uri-svg: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' x='0px' y='0px' viewBox='0 0 24 24' ><path fill='%23fff' d='M12,13.1l5-4.9l1.4,1.4L12,15.9L5.6,9.5l1.4-1.4L12,13.1z'/></svg>"); +} + +.fr-fieldset--error .select2-container, +.fr-select-group--error .select2-container { + box-shadow: inset 0 -2px 0 0 var(--border-plain-error); +} + +.fr-select:disabled + .select2-container { + color: var(--text-disabled-grey); + box-shadow: inset 0 -2px 0 0 var(--border-disabled-grey); + + --data-uri-svg: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' x='0px' y='0px' viewBox='0 0 24 24' ><path fill='%23929292' d='M12,13.1l5-4.9l1.4,1.4L12,15.9L5.6,9.5l1.4-1.4L12,13.1z'/></svg>"); + background-image: var(--data-uri-svg); +} + +:root[data-fr-theme=dark] .fr-select:disabled + .select2-container { + --data-uri-svg: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' x='0px' y='0px' viewBox='0 0 24 24' ><path fill='%23666' d='M12,13.1l5-4.9l1.4,1.4L12,15.9L5.6,9.5l1.4-1.4L12,13.1z'/></svg>"); + cursor: not-allowed; +} + +.select2-container:-webkit-autofill, +.select2-container:-webkit-autofill:hover, +.select2-container:-webkit-autofill:focus { + box-shadow: inset 0 -2px 0 0 var(--border-plain-grey), inset 0 0 0 1000px var(--background-contrast-blue-france); + -webkit-text-fill-color: var(--text-label-grey); +} + +@media (-ms-high-contrast: active), (forced-colors: active) { + .select2-container { + background-image: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' x='0px' y='0px' viewBox='0 0 24 24' fill='canvastext'><path d='M12,13.1l5-4.9l1.4,1.4L12,15.9L5.6,9.5l1.4-1.4L12,13.1z'/></svg>"); + } +} +/* Duplicate "select" component styles - END */ + +.select2-container .select2-selection--multiple { + background-color: transparent; + padding: 0; + display: block; +} + +.select2-container .select2-selection--multiple { + min-height: auto; +} + +.select2-container .select2-selection--multiple, +.select2-container.select2-container--focus .select2-selection--multiple { + border: 0; +} + +.select2-container .select2-search { + display: inline-block; +} + +.select2-container .select2-search--inline .select2-search__field { + margin: 0; + font-size: inherit; + font-family: "Marianne", arial, sans-serif; + height: 24px; +} + +.select2-container .select2-search--inline .select2-search__field::placeholder { + color: var(--text-default-grey); +} + +.select2-container .select2-selection--single .select2-selection__placeholder { + color: var(--text-mention-grey); +} + +.select2-dropdown { + margin-top: 2px; + padding: .25rem; + background-color: var(--background-contrast-grey); +} + +.select2-container--open .select2-dropdown--below { + border: 1px solid var(--border-plain-grey); +} + +.select2-container--default .select2-results__option--highlighted.select2-results__option--selectable { + background-color: var(--background-contrast-grey-hover); + color: var(--text-default-grey); +} + +.select2-selection__rendered .select2-selection__choice { + margin-right: .5rem; +} + +.select2-container--default .select2-selection--multiple .select2-selection__choice { + background-color: var(--background-action-low-blue-france); + color: var(--text-action-high-blue-france); + border-radius: 1rem; + font-size: .875rem; + padding: .25rem .75rem .25rem 1.25rem; + border: 0; +} +.select2-container--default .select2-selection--multiple .select2-selection__choice__remove { + border: 0; + color: var(--text-action-high-blue-france); + font-size: 1.5em; + padding: 0 .25rem; + left: .2rem; +} +.select2-container--default .select2-selection--multiple .select2-selection__choice__remove:hover, +.select2-container--default .select2-selection--multiple .select2-selection__choice__remove:focus { + background-color: transparent; + color: inherit; +} diff --git a/dsfr4drupal.info.yml b/dsfr4drupal.info.yml index ce69ee7..a563b75 100644 --- a/dsfr4drupal.info.yml +++ b/dsfr4drupal.info.yml @@ -62,6 +62,8 @@ libraries-extend: - dsfr4drupal/drupal.paragraphs.admin paragraphs/drupal.paragraphs.widget: - dsfr4drupal/drupal.paragraphs.widget + select2/select2: + - dsfr4drupal/select2 tarte_au_citron/tarte_au_citron_lib: - dsfr4drupal/tarteaucitron tacjs/tarteaucitron.js: diff --git a/dsfr4drupal.libraries.yml b/dsfr4drupal.libraries.yml index a081e62..05e20d9 100644 --- a/dsfr4drupal.libraries.yml +++ b/dsfr4drupal.libraries.yml @@ -649,6 +649,11 @@ scheme: dependencies: - dsfr4drupal/core +select2: + css: + theme: + css/theme/select2.css: {} + tarteaucitron: css: theme: -- GitLab From 50defcd43ddf19511a530685f088fb6f22e20c9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20BRINDLE?= <sebastien.brindle@kleegroup.com> Date: Mon, 17 Feb 2025 16:06:46 +0100 Subject: [PATCH 24/45] Fix error when field has not description. --- includes/form.theme | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/form.theme b/includes/form.theme index 74935ab..22707eb 100644 --- a/includes/form.theme +++ b/includes/form.theme @@ -146,7 +146,7 @@ function dsfr4drupal_preprocess_form_element(array &$variables): void { */ function dsfr4drupal_preprocess_form_element_label(array &$variables): void { // We need to have description in form element label template. - $variables['description'] = $variables['element']['#description']; + $variables['description'] = $variables['element']['#description'] ?? ''; // Show help text as tooltip. $variables['description_tooltip'] = theme_get_setting('form_description_tooltip') ?? FALSE; -- GitLab From 3d674c8f0ee7284573954cf855f9e4f3c8984291 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20BRINDLE?= <sebastien.brindle@kleegroup.com> Date: Mon, 17 Feb 2025 16:25:48 +0100 Subject: [PATCH 25/45] Fix dismiss rendering. --- css/theme/select2.css | 42 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 36 insertions(+), 6 deletions(-) diff --git a/css/theme/select2.css b/css/theme/select2.css index 24d8b4d..24754b6 100644 --- a/css/theme/select2.css +++ b/css/theme/select2.css @@ -119,22 +119,52 @@ } .select2-container--default .select2-selection--multiple .select2-selection__choice { - background-color: var(--background-action-low-blue-france); - color: var(--text-action-high-blue-france); + position: relative; + display: inline-flex; + background-color: var(--background-action-high-blue-france); + color: var(--text-inverted-blue-france); border-radius: 1rem; font-size: .875rem; - padding: .25rem .75rem .25rem 1.25rem; + padding: .25rem .75rem; border: 0; } + +.select2-container--default .select2-selection--multiple .select2-selection__choice:hover { + background-color: var(--background-action-high-blue-france-hover); +} + .select2-container--default .select2-selection--multiple .select2-selection__choice__remove { + position: initial; + left: auto; + top: auto; + display: flex; + order: 2; + padding: 0 .25rem; border: 0; - color: var(--text-action-high-blue-france); font-size: 1.5em; - padding: 0 .25rem; - left: .2rem; + line-height: 1; + color: var(--text-inverted-blue-france); +} + +.select2-container--default .select2-selection--multiple .select2-selection__choice__remove::before { + content: ""; + position: absolute; + display: block; + bottom: 0; + left: 0; + right: 0; + top: 0; + width: 100%; + height: 100%; + z-index: 1; } + .select2-container--default .select2-selection--multiple .select2-selection__choice__remove:hover, .select2-container--default .select2-selection--multiple .select2-selection__choice__remove:focus { background-color: transparent; color: inherit; } + +.select2-container--default .select2-selection--multiple .select2-selection__choice__display { + display: flex; +} -- GitLab From 0dd237f7f4faf2152e76e98d16e3323da62c6ac2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20BRINDLE?= <sebastien.brindle@kleegroup.com> Date: Tue, 25 Feb 2025 13:29:37 +0100 Subject: [PATCH 26/45] Fix accordion in horizontal/vertical tabs. --- css/theme/horizontal-tabs.css | 1 + css/theme/vertical-tabs.css | 1 + 2 files changed, 2 insertions(+) diff --git a/css/theme/horizontal-tabs.css b/css/theme/horizontal-tabs.css index f0d6610..191b5f3 100644 --- a/css/theme/horizontal-tabs.css +++ b/css/theme/horizontal-tabs.css @@ -56,6 +56,7 @@ padding: 0; } +.horizontal-tabs-panes .fr-accordion__btn, .horizontal-tabs-panes .fr-input, .horizontal-tabs-panes .fr-select { box-sizing: border-box; diff --git a/css/theme/vertical-tabs.css b/css/theme/vertical-tabs.css index 43ae05c..120a651 100644 --- a/css/theme/vertical-tabs.css +++ b/css/theme/vertical-tabs.css @@ -44,6 +44,7 @@ padding: var(--form-spacing-xs) var(--form-spacing) var(--form-spacing-s); } +.vertical-tabs__pane .fr-accordion__btn, .vertical-tabs__pane .fr-input, .vertical-tabs__pane .fr-select { box-sizing: border-box; -- GitLab From 79cd12b29a3c8738e4a2b70c8b7a366e09df0ab8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20BRINDLE?= <sebastien.brindle@kleegroup.com> Date: Tue, 4 Mar 2025 15:44:08 +0100 Subject: [PATCH 27/45] Move core.css to base directory. --- css/{ => base}/core.css | 0 dsfr4drupal.libraries.yml | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename css/{ => base}/core.css (100%) diff --git a/css/core.css b/css/base/core.css similarity index 100% rename from css/core.css rename to css/base/core.css diff --git a/dsfr4drupal.libraries.yml b/dsfr4drupal.libraries.yml index be6ca7d..590ba7a 100644 --- a/dsfr4drupal.libraries.yml +++ b/dsfr4drupal.libraries.yml @@ -527,7 +527,7 @@ core: css: base: /libraries/dsfr/dist/core/core.min.css: { minified: true } - css/core.css: {} + css/base/core.css: {} js: js/core.js: {} /libraries/dsfr/dist/core/core.module.min.js: -- GitLab From 30d6dee9b1e6600dfd2db63035dc6f7394709a76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20BRINDLE?= <sebastien.brindle@kleegroup.com> Date: Tue, 4 Mar 2025 16:02:33 +0100 Subject: [PATCH 28/45] Reduce filter text size. --- css/component/form.css | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/css/component/form.css b/css/component/form.css index f612baf..d10c493 100644 --- a/css/component/form.css +++ b/css/component/form.css @@ -18,14 +18,12 @@ /* Provide a class to remove spacing. */ .dsfr4drupal-form--no-spacing { --form-spacing: 0; - --form-item-spacing: 0; } /* Unset form spacing for default DSFR forms. */ .fr-follow__newsletter, .fr-search-bar { --form-spacing: 0; - --form-item-spacing: 0; } main[role="main"] > .fr-container > form, @@ -33,10 +31,22 @@ main[role="main"] > .fr-container--fluid > form { margin-block: var(--form-spacing); } +.fr-label > .field-edit-link { + font-size: .75em; +} + +.fr-label .field-edit-link > button { + padding: 0; +} + .form-wrapper { margin-block: var(--form-item-spacing); } +.form-wrapper.js-filter-wrapper { + font-size: .75em; +} + .form-actions a + button, .form-actions button + button, .form-actions button + a { -- GitLab From cc23ce00c85179acbcf8ab2e2e74f9a9290f8c93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20BRINDLE?= <sebastien.brindle@kleegroup.com> Date: Wed, 5 Mar 2025 12:06:34 +0100 Subject: [PATCH 29/45] Fix feedbacks. --- includes/media.theme | 18 ------------- js/paragraphs.widget.js | 5 +++- templates/block/block.html.twig | 20 +++++++------- .../media/media--media-library.html.twig | 26 +++++++++---------- templates/media/media.html.twig | 8 +++--- templates/node/node.html.twig | 14 +++++----- templates/region/region.html.twig | 6 ++--- .../views/views-view--media-library.html.twig | 8 +++--- 8 files changed, 44 insertions(+), 61 deletions(-) delete mode 100644 includes/media.theme diff --git a/includes/media.theme b/includes/media.theme deleted file mode 100644 index d98dfcb..0000000 --- a/includes/media.theme +++ /dev/null @@ -1,18 +0,0 @@ -<?php - -/** - * @file - * Functions to support media theming in the "DSFR for Drupal" theme. - */ - -declare(strict_types=1); - -/** - * Implements hook_preprocss_hook() for "media__media_library". - */ -function dsfr4drupal_preprocess_media__media_library(array &$variables): void { - /** @var \Drupal\media\MediaInterface $media */ - $media = $variables['media']; - - -} diff --git a/js/paragraphs.widget.js b/js/paragraphs.widget.js index bd349eb..786664b 100644 --- a/js/paragraphs.widget.js +++ b/js/paragraphs.widget.js @@ -1,4 +1,7 @@ - +/** + * @file + * Manage paragraphs widget rendering with DSFR styles. + */ (function (Drupal, once) { "use strict"; diff --git a/templates/block/block.html.twig b/templates/block/block.html.twig index 1ac5205..221b492 100644 --- a/templates/block/block.html.twig +++ b/templates/block/block.html.twig @@ -1,16 +1,16 @@ {% if attributes is not empty %} -<div{{ attributes }}> + <div{{ attributes }}> {% endif %} - {{ title_prefix }} - {% if label %} - <h2{{ title_attributes }}>{{ label }}</h2> - {% endif %} - {{ title_suffix }} - {% block content %} - {{ content }} - {% endblock %} +{{ title_prefix }} +{% if label %} + <h2{{ title_attributes }}>{{ label }}</h2> +{% endif %} +{{ title_suffix }} +{% block content %} + {{ content }} +{% endblock %} {% if attributes is not empty %} -</div> + </div> {% endif %} diff --git a/templates/media/media--media-library.html.twig b/templates/media/media--media-library.html.twig index 9f70bd2..e51ace7 100644 --- a/templates/media/media--media-library.html.twig +++ b/templates/media/media--media-library.html.twig @@ -1,19 +1,19 @@ {% if attributes is not empty %} -<div{{ attributes }}> + <div{{ attributes }}> {% endif %} - <div{{ preview_attributes.addClass('js-media-library-item-preview') }}> - {{ include('dsfr4drupal:card', { - 'image': content.thumbnail ? content.thumbnail : '', - 'title': name, - 'title_attributes': metadata_attributes, - 'description': content|without('thumbnail'), - 'no_arrow': true, - 'detail': not status ? 'unpublished'|t : '', - 'detail_icon': 'fr-icon-warning-fill', - 'variant': 'sm', - }, with_context=false) }} - </div> +<div{{ preview_attributes.addClass('js-media-library-item-preview') }}> + {{ include('dsfr4drupal:card', { + 'image': content.thumbnail ? content.thumbnail : '', + 'title': name, + 'title_attributes': metadata_attributes, + 'description': content|without('thumbnail'), + 'no_arrow': true, + 'detail': not status ? 'unpublished'|t : '', + 'detail_icon': 'fr-icon-warning-fill', + 'variant': 'sm', + }, with_context=false) }} +</div> {% if attributes is not empty %} </div> diff --git a/templates/media/media.html.twig b/templates/media/media.html.twig index 427b3f5..349904d 100644 --- a/templates/media/media.html.twig +++ b/templates/media/media.html.twig @@ -1,10 +1,10 @@ {% if attributes is not empty %} -<div{{ attributes }}> + <div{{ attributes }}> {% endif %} - {{ title_suffix.contextual_links }} - {{ content }} +{{ title_suffix.contextual_links }} +{{ content }} {% if attributes is not empty %} -</div> + </div> {% endif %} diff --git a/templates/node/node.html.twig b/templates/node/node.html.twig index b23d6da..f46d52d 100644 --- a/templates/node/node.html.twig +++ b/templates/node/node.html.twig @@ -1,17 +1,17 @@ {% if attributes is not empty %} -<div{{ attributes }}> + <div{{ attributes }}> {% endif %} - {% if content_attributes is not empty %} +{% if content_attributes is not empty %} <div{{ content_attributes }}> - {% endif %} +{% endif %} - {{ content }} +{{ content }} - {% if content_attributes is not empty %} +{% if content_attributes is not empty %} </div> - {% endif %} +{% endif %} {% if attributes is not empty %} -</div> + </div> {% endif %} diff --git a/templates/region/region.html.twig b/templates/region/region.html.twig index ce19558..80f6ad3 100644 --- a/templates/region/region.html.twig +++ b/templates/region/region.html.twig @@ -1,11 +1,11 @@ {% if content %} {% if attributes is not empty %} - <div{{ attributes }}> + <div{{ attributes }}> {% endif %} - {{ content }} + {{ content }} {% if attributes is not empty %} - </div> + </div> {% endif %} {% endif %} diff --git a/templates/views/views-view--media-library.html.twig b/templates/views/views-view--media-library.html.twig index 0b66c96..d31d2c2 100644 --- a/templates/views/views-view--media-library.html.twig +++ b/templates/views/views-view--media-library.html.twig @@ -1,9 +1,7 @@ - -{% - set classes = [ +{% set classes = [ dom_id ? 'js-view-dom-id-' ~ dom_id, -] -%} +] %} + <div{{ attributes.addClass(classes) }}> {{ title_prefix }} {{ title }} -- GitLab From 5794d54c57b2a8401725f444ba50e50b6879ac60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20BRINDLE?= <sebastien.brindle@kleegroup.com> Date: Wed, 5 Mar 2025 12:14:58 +0100 Subject: [PATCH 30/45] Fix feedbacks. --- includes/form.theme | 4 ++-- includes/system.theme | 1 - js/accordion.js | 1 + js/horizontal-tabs.js | 1 + js/tabledrag.js | 5 +++-- 5 files changed, 7 insertions(+), 5 deletions(-) diff --git a/includes/form.theme b/includes/form.theme index 53aefc0..db0eeb8 100644 --- a/includes/form.theme +++ b/includes/form.theme @@ -73,7 +73,7 @@ function dsfr4drupal_preprocess_form(array &$variables): void { /** * Implements hook_preprocess_HOOK() for "views_exposed_form". */ -function dsfr4drupal_preprocess_views_exposed_form(&$variables) { +function dsfr4drupal_preprocess_views_exposed_form(array &$variables): void { $form = &$variables['form']; // Add BEM classes for items in the form. @@ -81,7 +81,7 @@ function dsfr4drupal_preprocess_views_exposed_form(&$variables) { foreach (Element::children($form, TRUE) as $child_key) { if (!empty($form[$child_key]['#type'])) { if ($form[$child_key]['#type'] === 'actions') { - // We need the key of the element that precedes the actions element. + // We need the key of the element that precedes the actions' element. $form[$child_key]['#attributes']['class'][] = 'views-exposed-form__item'; $form[$child_key]['#attributes']['class'][] = 'views-exposed-form__item--actions'; } diff --git a/includes/system.theme b/includes/system.theme index 49f3b39..7b3bf79 100644 --- a/includes/system.theme +++ b/includes/system.theme @@ -7,7 +7,6 @@ declare(strict_types=1); - /** * Implements hook_theme_suggestions_HOOK_alter() for details. */ diff --git a/js/accordion.js b/js/accordion.js index 14679c1..e2202d9 100644 --- a/js/accordion.js +++ b/js/accordion.js @@ -4,6 +4,7 @@ */ ((Drupal, once) => { + "use strict"; Drupal.behaviors.dsfrAccordion = { attach: (context) => { diff --git a/js/horizontal-tabs.js b/js/horizontal-tabs.js index cb1604a..a58d335 100644 --- a/js/horizontal-tabs.js +++ b/js/horizontal-tabs.js @@ -4,6 +4,7 @@ */ ((Drupal, once) => { + "use strict"; const CLASS_LIST = "fr-tabs__list"; const CLASS_TAB = "fr-tabs__tab"; diff --git a/js/tabledrag.js b/js/tabledrag.js index da730da..1c3db8c 100644 --- a/js/tabledrag.js +++ b/js/tabledrag.js @@ -10,6 +10,7 @@ */ (function ($, Drupal, drupalSettings) { + "use strict"; /** * Store the state of weight columns display for all tables. @@ -24,7 +25,7 @@ const addChangedWarningOriginal = Drupal.tableDrag.prototype.row.prototype.addChangedWarning; /** - @inheritDoc + * {@inheritdoc} */ Drupal.tableDrag.prototype.row.prototype.addChangedWarning = function () { const $table = $(this.table.parentNode); @@ -41,7 +42,7 @@ }; /** - * @inheritDoc + * {@inheritdoc} */ Drupal.tableDrag.prototype.initColumns = function () { const $tableWrapper = this.$table.parents(".fr-table"); -- GitLab From c48cfd677155caefea8b2a0c84ffe527588e0484 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20BRINDLE?= <sebastien.brindle@kleegroup.com> Date: Wed, 5 Mar 2025 14:19:53 +0100 Subject: [PATCH 31/45] Fix feedbacks. --- css/ckeditor5.css | 27 +++++++++++++++++++++- css/component/checkbox.css | 1 + css/component/form.css | 16 ++++++------- css/theme/horizontal-tabs.css | 13 +++++------ css/theme/media-library.css | 2 +- css/theme/select2.css | 43 ++++++++++++++++------------------- css/theme/ui-dialog.css | 13 +++++++---- css/theme/vertical-tabs.css | 14 ++++++------ dsfr4drupal.libraries.yml | 3 +++ js/horizontal-tabs.js | 7 +----- js/paragraphs.widget.js | 1 - js/tabledrag.js | 36 ++++++++++++----------------- 12 files changed, 94 insertions(+), 82 deletions(-) diff --git a/css/ckeditor5.css b/css/ckeditor5.css index a1ca5da..94e00f6 100644 --- a/css/ckeditor5.css +++ b/css/ckeditor5.css @@ -111,7 +111,7 @@ --underline-thickness: 0.0625em; --underline-img: linear-gradient(0deg, currentcolor, currentcolor); --xl-base: 1em; - --ol-content: counters(li-counter, ".") ".  "; + --ol-content: counters(li-counter, ".") ".  "; /* stylelint-disable-line no-irregular-whitespace */ color: var(--text-default-grey); font-family: Marianne,arial,sans-serif; @@ -252,78 +252,103 @@ .ck-content ol[start] { counter-reset: none; } + .ck-content ol[start="0"] { counter-reset: li-counter -1; } + .ck-content ol[start="2"] { counter-reset: li-counter 1; } + .ck-content ol[start="3"] { counter-reset: li-counter 2; } + .ck-content ol[start="4"] { counter-reset: li-counter 3; } + .ck-content ol[start="5"] { counter-reset: li-counter 4; } + .ck-content ol[start="6"] { counter-reset: li-counter 5; } + .ck-content ol[start="7"] { counter-reset: li-counter 6; } + .ck-content ol[start="8"] { counter-reset: li-counter 7; } + .ck-content ol[start="9"] { counter-reset: li-counter 8; } + .ck-content ol[start="10"] { counter-reset: li-counter 9; } + .ck-content ol[start="11"] { counter-reset: li-counter 10; } + .ck-content ol[start="12"] { counter-reset: li-counter 11; } + .ck-content ol[start="13"] { counter-reset: li-counter 12; } + .ck-content ol[start="14"] { counter-reset: li-counter 13; } + .ck-content ol[start="15"] { counter-reset: li-counter 14; } + .ck-content ol[start="16"] { counter-reset: li-counter 15; } + .ck-content ol[start="17"] { counter-reset: li-counter 16; } + .ck-content ol[start="18"] { counter-reset: li-counter 17; } + .ck-content ol[start="19"] { counter-reset: li-counter 18; } + .ck-content ol[start="20"] { counter-reset: li-counter 19; } + .ck-content ol[start="21"] { counter-reset: li-counter 20; } + .ck-content ol[start="22"] { counter-reset: li-counter 21; } + .ck-content ol[start="23"] { counter-reset: li-counter 22; } + .ck-content ol[start="24"] { counter-reset: li-counter 23; } + .ck-content ol[start="25"] { counter-reset: li-counter 24; } diff --git a/css/component/checkbox.css b/css/component/checkbox.css index f41a5f5..412003e 100644 --- a/css/component/checkbox.css +++ b/css/component/checkbox.css @@ -13,6 +13,7 @@ clip: auto; z-index: 1; } + .fr-checkbox-group input[type="checkbox"] + label.visually-hidden::before { left: 0; } diff --git a/css/component/form.css b/css/component/form.css index d10c493..4dc6fb9 100644 --- a/css/component/form.css +++ b/css/component/form.css @@ -31,14 +31,6 @@ main[role="main"] > .fr-container--fluid > form { margin-block: var(--form-spacing); } -.fr-label > .field-edit-link { - font-size: .75em; -} - -.fr-label .field-edit-link > button { - padding: 0; -} - .form-wrapper { margin-block: var(--form-item-spacing); } @@ -76,6 +68,14 @@ main[role="main"] > .fr-container--fluid > form { border: 1px solid var(--border-default-grey); } +.fr-label > .field-edit-link { + font-size: .75em; +} + +.fr-label .field-edit-link > button { + padding: 0; +} + .fr-upload-group { padding: var(--form-spacing-xs) var(--form-spacing) var(--form-spacing); border: 1px solid var(--border-default-grey); diff --git a/css/theme/horizontal-tabs.css b/css/theme/horizontal-tabs.css index 191b5f3..6943d5d 100644 --- a/css/theme/horizontal-tabs.css +++ b/css/theme/horizontal-tabs.css @@ -20,8 +20,7 @@ padding: 4px .75rem; } -.horizontal-tabs ul.horizontal-tabs-list li a, -.horizontal-tabs ul.horizontal-tabs-list li.selected a { +.horizontal-tabs ul.horizontal-tabs-list li a { /* Force DSFR tabs styles */ display: inline-flex; padding: .5rem 1rem; @@ -41,11 +40,6 @@ background-color: var(--hover-tint); } -.horizontal-tabs-list.fr-tabs__list + .horizontal-tabs-panes { - /* Fix order */ - order: 3; -} - .horizontal-tabs-panes { border: 1px solid var(--border-default-grey); border-top: 0; @@ -56,6 +50,11 @@ padding: 0; } +.horizontal-tabs-list.fr-tabs__list + .horizontal-tabs-panes { + /* Fix order */ + order: 3; +} + .horizontal-tabs-panes .fr-accordion__btn, .horizontal-tabs-panes .fr-input, .horizontal-tabs-panes .fr-select { diff --git a/css/theme/media-library.css b/css/theme/media-library.css index e1bddc1..e5d4c7b 100644 --- a/css/theme/media-library.css +++ b/css/theme/media-library.css @@ -13,7 +13,7 @@ } .js-media-library-item { - margin-block: var(--form-item-spacing) !important; + margin-block: var(--form-item-spacing) !important; /* stylelint-disable-line declaration-no-important */ } .js-media-library-add-form-added-media { diff --git a/css/theme/select2.css b/css/theme/select2.css index 24754b6..7552863 100644 --- a/css/theme/select2.css +++ b/css/theme/select2.css @@ -6,8 +6,6 @@ /* Duplicate "select" component styles - START */ .select2-container .selection { display: block; - -webkit-appearance: none; - -moz-appearance: none; appearance: none; width: 100%; border-radius: 0.25rem 0.25rem 0 0; @@ -23,14 +21,12 @@ --idle: transparent; --hover: var(--background-contrast-grey-hover); --active: var(--background-contrast-grey-active); + box-shadow: inset 0 -2px 0 0 var(--border-plain-grey); --data-uri-svg: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' x='0px' y='0px' viewBox='0 0 24 24' ><path fill='%23161616' d='M12,13.1l5-4.9l1.4,1.4L12,15.9L5.6,9.5l1.4-1.4L12,13.1z'/></svg>"); - background-image: var(--data-uri-svg); -} -:root[data-fr-theme=dark] .select2-container { - --data-uri-svg: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' x='0px' y='0px' viewBox='0 0 24 24' ><path fill='%23fff' d='M12,13.1l5-4.9l1.4,1.4L12,15.9L5.6,9.5l1.4-1.4L12,13.1z'/></svg>"); + background-image: var(--data-uri-svg); } .fr-fieldset--error .select2-container, @@ -38,24 +34,29 @@ box-shadow: inset 0 -2px 0 0 var(--border-plain-error); } +.select2-container:-webkit-autofill, +.select2-container:-webkit-autofill:hover, +.select2-container:-webkit-autofill:focus { + box-shadow: inset 0 -2px 0 0 var(--border-plain-grey), inset 0 0 0 1000px var(--background-contrast-blue-france); + -webkit-text-fill-color: var(--text-label-grey); +} + .fr-select:disabled + .select2-container { + --data-uri-svg: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' x='0px' y='0px' viewBox='0 0 24 24' ><path fill='%23929292' d='M12,13.1l5-4.9l1.4,1.4L12,15.9L5.6,9.5l1.4-1.4L12,13.1z'/></svg>"); + color: var(--text-disabled-grey); box-shadow: inset 0 -2px 0 0 var(--border-disabled-grey); - - --data-uri-svg: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' x='0px' y='0px' viewBox='0 0 24 24' ><path fill='%23929292' d='M12,13.1l5-4.9l1.4,1.4L12,15.9L5.6,9.5l1.4-1.4L12,13.1z'/></svg>"); background-image: var(--data-uri-svg); } -:root[data-fr-theme=dark] .fr-select:disabled + .select2-container { - --data-uri-svg: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' x='0px' y='0px' viewBox='0 0 24 24' ><path fill='%23666' d='M12,13.1l5-4.9l1.4,1.4L12,15.9L5.6,9.5l1.4-1.4L12,13.1z'/></svg>"); - cursor: not-allowed; +:root[data-fr-theme="dark"] .select2-container { + --data-uri-svg: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' x='0px' y='0px' viewBox='0 0 24 24' ><path fill='%23fff' d='M12,13.1l5-4.9l1.4,1.4L12,15.9L5.6,9.5l1.4-1.4L12,13.1z'/></svg>"); } -.select2-container:-webkit-autofill, -.select2-container:-webkit-autofill:hover, -.select2-container:-webkit-autofill:focus { - box-shadow: inset 0 -2px 0 0 var(--border-plain-grey), inset 0 0 0 1000px var(--background-contrast-blue-france); - -webkit-text-fill-color: var(--text-label-grey); +:root[data-fr-theme="dark"] .fr-select:disabled + .select2-container { + --data-uri-svg: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' x='0px' y='0px' viewBox='0 0 24 24' ><path fill='%23666' d='M12,13.1l5-4.9l1.4,1.4L12,15.9L5.6,9.5l1.4-1.4L12,13.1z'/></svg>"); + + cursor: not-allowed; } @media (-ms-high-contrast: active), (forced-colors: active) { @@ -69,9 +70,6 @@ background-color: transparent; padding: 0; display: block; -} - -.select2-container .select2-selection--multiple { min-height: auto; } @@ -87,7 +85,7 @@ .select2-container .select2-search--inline .select2-search__field { margin: 0; font-size: inherit; - font-family: "Marianne", arial, sans-serif; + font-family: Marianne, arial, sans-serif; height: 24px; } @@ -150,10 +148,7 @@ content: ""; position: absolute; display: block; - bottom: 0; - left: 0; - right: 0; - top: 0; + inset: 0; width: 100%; height: 100%; z-index: 1; diff --git a/css/theme/ui-dialog.css b/css/theme/ui-dialog.css index f8e44b7..a682040 100644 --- a/css/theme/ui-dialog.css +++ b/css/theme/ui-dialog.css @@ -5,7 +5,8 @@ */ .ui-dialog { - padding: 0; max-height: 80vh !important; + padding: 0; + max-height: 80vh !important; /* stylelint-disable-line declaration-no-important */ filter: drop-shadow(var(--lifted-shadow)); } @@ -29,6 +30,7 @@ background: var(--background-default-grey); color: var(--text-default-grey); } + .ui-widget.ui-widget-content { border: 0; } @@ -41,7 +43,7 @@ font-weight: 700; } -@media (min-width: 48em) { +@media (width >= 48em) { .ui-widget-header { font-size: 1.5rem; line-height: 2rem; @@ -66,6 +68,7 @@ line-height: 1.5rem; overflow: initial; } + .ui-dialog .ui-dialog-titlebar-close:hover { background-color: var(--hover-tint); } @@ -73,16 +76,16 @@ .ui-dialog .ui-dialog-titlebar-close::after { --icon-size: 1rem; - background-color: currentColor; + background-color: currentcolor; content: ''; display: inline-block; flex: 0 0 auto; height: var(--icon-size); margin-left: .5rem; margin-right: -.125rem; - mask-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCI+PHBhdGggZD0ibTEyIDEwLjYgNC45NS00Ljk2IDEuNCAxLjRMMTMuNDIgMTJsNC45NiA0Ljk1LTEuNCAxLjRMMTIgMTMuNDJsLTQuOTUgNC45Ni0xLjQtMS40TDEwLjU4IDEyIDUuNjMgNy4wNWwxLjQtMS40eiIvPjwvc3ZnPg==); + mask-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCI+PHBhdGggZD0ibTEyIDEwLjYgNC45NS00Ljk2IDEuNCAxLjRMMTMuNDIgMTJsNC45NiA0Ljk1LTEuNCAxLjRMMTIgMTMuNDJsLTQuOTUgNC45Ni0xLjQtMS40TDEwLjU4IDEyIDUuNjMgNy4wNWwxLjQtMS40eiIvPjwvc3ZnPg=="); mask-size: 100% 100%; - vertical-align: calc((.75em - var(--icon-size))*.5); + vertical-align: calc((.75em - var(--icon-size)) * .5); width: var(--icon-size); } diff --git a/css/theme/vertical-tabs.css b/css/theme/vertical-tabs.css index 120a651..bf97ad7 100644 --- a/css/theme/vertical-tabs.css +++ b/css/theme/vertical-tabs.css @@ -15,6 +15,13 @@ outline: none; } +.vertical-tabs__menu-item.is-selected a { + background-color: var(--background-default-grey); + background-image: linear-gradient(0deg,var(--border-active-blue-france),var(--border-active-blue-france)),linear-gradient(0deg,var(--border-default-grey),var(--border-default-grey)),linear-gradient(0deg,var(--border-default-grey),var(--border-default-grey)),linear-gradient(0deg,var(--border-default-grey),var(--border-default-grey)); + background-size: 100% 2px,1px calc(100% - 1px),1px calc(100% - 1px),0 1px; + color: var(--text-active-blue-france); +} + .vertical-tabs__menu-item:not(.is-selected) a { background-color: var(--background-action-low-blue-france); } @@ -23,13 +30,6 @@ background-color: var(--background-action-low-blue-france-hover); } -.vertical-tabs__menu-item.is-selected a { - background-color: var(--background-default-grey); - background-image: linear-gradient(0deg,var(--border-active-blue-france),var(--border-active-blue-france)),linear-gradient(0deg,var(--border-default-grey),var(--border-default-grey)),linear-gradient(0deg,var(--border-default-grey),var(--border-default-grey)),linear-gradient(0deg,var(--border-default-grey),var(--border-default-grey)); - background-size: 100% 2px,1px calc(100% - 1px),1px calc(100% - 1px),0 1px; - color: var(--text-active-blue-france); -} - .vertical-tabs__menu-item.is-selected .vertical-tabs__menu-item-title { color: inherit; } diff --git a/dsfr4drupal.libraries.yml b/dsfr4drupal.libraries.yml index 590ba7a..3d5621e 100644 --- a/dsfr4drupal.libraries.yml +++ b/dsfr4drupal.libraries.yml @@ -601,6 +601,9 @@ drupal.tabledrag: css/component/tabledrag.css: {} js: js/tabledrag.js: {} + dependencies: + - core/drupal + - core/jquery drupal.vertical-tabs: css: diff --git a/js/horizontal-tabs.js b/js/horizontal-tabs.js index a58d335..030a42d 100644 --- a/js/horizontal-tabs.js +++ b/js/horizontal-tabs.js @@ -17,19 +17,14 @@ // Search all horizontal tabs in current context. once("dsfr-horizontal-tabs", ".horizontal-tabs", context).forEach((element) => { const wrapper = element.querySelector(`.${CLASS_TABS}`); - if (!wrapper) { - return; - } - const list = element.querySelector(`.horizontal-tabs-list.${CLASS_LIST}`); - if (!list) { + if (!wrapper || !list) { return; } wrapper.append(list); list.querySelectorAll("li").forEach((item) => { - const link = item.querySelector("a"); link.classList.add(CLASS_TAB); diff --git a/js/paragraphs.widget.js b/js/paragraphs.widget.js index 786664b..d9bb779 100644 --- a/js/paragraphs.widget.js +++ b/js/paragraphs.widget.js @@ -54,7 +54,6 @@ }) }); }); - }, }; diff --git a/js/tabledrag.js b/js/tabledrag.js index 1c3db8c..5b6ecea 100644 --- a/js/tabledrag.js +++ b/js/tabledrag.js @@ -9,18 +9,9 @@ * @event columnschange */ -(function ($, Drupal, drupalSettings) { +(function ($, Drupal) { "use strict"; - /** - * Store the state of weight columns display for all tables. - * - * Default value is to hide weight columns. - */ - let showWeight = JSON.parse( - localStorage.getItem("Drupal.tableDrag.showWeight"), - ); - const initColumnsOriginal = Drupal.tableDrag.prototype.initColumns; const addChangedWarningOriginal = Drupal.tableDrag.prototype.row.prototype.addChangedWarning; @@ -31,13 +22,13 @@ const $table = $(this.table.parentNode); // Do not add the changed warning if one is already present. - if (!$table.find('.tabledrag-changed-warning').length) { + if (!$table.find(".tabledrag-changed-warning").length) { addChangedWarningOriginal.call(this); const $tableWrapper = $table.parents(".fr-table"); // Move changed warning message before DSFR table wrapper. - $tableWrapper.parent().prepend($table.find('.tabledrag-changed-warning')); + $tableWrapper.parent().prepend($table.find(".tabledrag-changed-warning")); } }; @@ -69,20 +60,21 @@ */ toggleButtonContent: (show) => { const classes = [ - 'tabledrag-toggle-weight', - 'fr-icon--sm', + "tabledrag-toggle-weight", + "fr-icon--sm", ]; - let text = ''; + let text = ""; if (show) { - classes.push('fr-icon-eye-off-line'); - text = Drupal.t('Hide row weights'); - } else { - classes.push('fr-icon-eye-line'); - text = Drupal.t('Show row weights'); + classes.push("fr-icon-eye-off-line"); + text = Drupal.t("Hide row weights"); + } + else { + classes.push("fr-icon-eye-line"); + text = Drupal.t("Show row weights"); } - return `<span class="${classes.join(' ')}" aria-hidden="true"></span> ${text}`; + return `<span class="${classes.join(" ")}" aria-hidden="true"></span> ${text}`; }, }, ); -})(jQuery, Drupal, drupalSettings); +})(jQuery, Drupal); -- GitLab From 47e559fba2a70b803ce4d7321b8515898fdd18c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20BRINDLE?= <sebastien.brindle@kleegroup.com> Date: Tue, 18 Mar 2025 15:31:03 +0100 Subject: [PATCH 32/45] Fix double styling in views exposed form. --- composer.lock | 18 + css/component/form.css | 8 - css/component/views-exposed-form.css | 2 +- vendor/autoload.php | 25 + vendor/composer/ClassLoader.php | 579 ++++++++++++++++++++++++ vendor/composer/InstalledVersions.php | 359 +++++++++++++++ vendor/composer/LICENSE | 21 + vendor/composer/autoload_classmap.php | 10 + vendor/composer/autoload_namespaces.php | 9 + vendor/composer/autoload_psr4.php | 9 + vendor/composer/autoload_real.php | 36 ++ vendor/composer/autoload_static.php | 20 + vendor/composer/installed.json | 5 + vendor/composer/installed.php | 23 + 14 files changed, 1115 insertions(+), 9 deletions(-) create mode 100644 composer.lock create mode 100644 vendor/autoload.php create mode 100644 vendor/composer/ClassLoader.php create mode 100644 vendor/composer/InstalledVersions.php create mode 100644 vendor/composer/LICENSE create mode 100644 vendor/composer/autoload_classmap.php create mode 100644 vendor/composer/autoload_namespaces.php create mode 100644 vendor/composer/autoload_psr4.php create mode 100644 vendor/composer/autoload_real.php create mode 100644 vendor/composer/autoload_static.php create mode 100644 vendor/composer/installed.json create mode 100644 vendor/composer/installed.php diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..c35d1e9 --- /dev/null +++ b/composer.lock @@ -0,0 +1,18 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "e2b57849f956d4c67658848cdb027ed8", + "packages": [], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [], + "plugin-api-version": "2.6.0" +} diff --git a/css/component/form.css b/css/component/form.css index 4dc6fb9..7ccda7c 100644 --- a/css/component/form.css +++ b/css/component/form.css @@ -60,14 +60,6 @@ main[role="main"] > .fr-container--fluid > form { } /* Disable table scrolling into form - END */ -.views-exposed-form { - display: flex; - flex-wrap: wrap; - margin-block: var(--form-spacing); - padding: var(--form-spacing-xs) var(--form-spacing) var(--form-spacing); - border: 1px solid var(--border-default-grey); -} - .fr-label > .field-edit-link { font-size: .75em; } diff --git a/css/component/views-exposed-form.css b/css/component/views-exposed-form.css index d465cc8..3ebbefc 100644 --- a/css/component/views-exposed-form.css +++ b/css/component/views-exposed-form.css @@ -10,8 +10,8 @@ display: flex; flex-wrap: wrap; margin-block: var(--form-spacing-s); - margin-inline: var(--form-spacing-s); padding: var(--form-spacing-xs) var(--form-spacing-s) var(--form-spacing-s); + border: 1px solid var(--border-default-grey); } .views-exposed-form--preview.views-exposed-form--preview { diff --git a/vendor/autoload.php b/vendor/autoload.php new file mode 100644 index 0000000..8d66326 --- /dev/null +++ b/vendor/autoload.php @@ -0,0 +1,25 @@ +<?php + +// autoload.php @generated by Composer + +if (PHP_VERSION_ID < 50600) { + if (!headers_sent()) { + header('HTTP/1.1 500 Internal Server Error'); + } + $err = 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL; + if (!ini_get('display_errors')) { + if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') { + fwrite(STDERR, $err); + } elseif (!headers_sent()) { + echo $err; + } + } + trigger_error( + $err, + E_USER_ERROR + ); +} + +require_once __DIR__ . '/composer/autoload_real.php'; + +return ComposerAutoloaderInite2b57849f956d4c67658848cdb027ed8::getLoader(); diff --git a/vendor/composer/ClassLoader.php b/vendor/composer/ClassLoader.php new file mode 100644 index 0000000..7824d8f --- /dev/null +++ b/vendor/composer/ClassLoader.php @@ -0,0 +1,579 @@ +<?php + +/* + * This file is part of Composer. + * + * (c) Nils Adermann <naderman@naderman.de> + * Jordi Boggiano <j.boggiano@seld.be> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Autoload; + +/** + * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. + * + * $loader = new \Composer\Autoload\ClassLoader(); + * + * // register classes with namespaces + * $loader->add('Symfony\Component', __DIR__.'/component'); + * $loader->add('Symfony', __DIR__.'/framework'); + * + * // activate the autoloader + * $loader->register(); + * + * // to enable searching the include path (eg. for PEAR packages) + * $loader->setUseIncludePath(true); + * + * In this example, if you try to use a class in the Symfony\Component + * namespace or one of its children (Symfony\Component\Console for instance), + * the autoloader will first look for the class under the component/ + * directory, and it will then fallback to the framework/ directory if not + * found before giving up. + * + * This class is loosely based on the Symfony UniversalClassLoader. + * + * @author Fabien Potencier <fabien@symfony.com> + * @author Jordi Boggiano <j.boggiano@seld.be> + * @see https://www.php-fig.org/psr/psr-0/ + * @see https://www.php-fig.org/psr/psr-4/ + */ +class ClassLoader +{ + /** @var \Closure(string):void */ + private static $includeFile; + + /** @var string|null */ + private $vendorDir; + + // PSR-4 + /** + * @var array<string, array<string, int>> + */ + private $prefixLengthsPsr4 = array(); + /** + * @var array<string, list<string>> + */ + private $prefixDirsPsr4 = array(); + /** + * @var list<string> + */ + private $fallbackDirsPsr4 = array(); + + // PSR-0 + /** + * List of PSR-0 prefixes + * + * Structured as array('F (first letter)' => array('Foo\Bar (full prefix)' => array('path', 'path2'))) + * + * @var array<string, array<string, list<string>>> + */ + private $prefixesPsr0 = array(); + /** + * @var list<string> + */ + private $fallbackDirsPsr0 = array(); + + /** @var bool */ + private $useIncludePath = false; + + /** + * @var array<string, string> + */ + private $classMap = array(); + + /** @var bool */ + private $classMapAuthoritative = false; + + /** + * @var array<string, bool> + */ + private $missingClasses = array(); + + /** @var string|null */ + private $apcuPrefix; + + /** + * @var array<string, self> + */ + private static $registeredLoaders = array(); + + /** + * @param string|null $vendorDir + */ + public function __construct($vendorDir = null) + { + $this->vendorDir = $vendorDir; + self::initializeIncludeClosure(); + } + + /** + * @return array<string, list<string>> + */ + public function getPrefixes() + { + if (!empty($this->prefixesPsr0)) { + return call_user_func_array('array_merge', array_values($this->prefixesPsr0)); + } + + return array(); + } + + /** + * @return array<string, list<string>> + */ + public function getPrefixesPsr4() + { + return $this->prefixDirsPsr4; + } + + /** + * @return list<string> + */ + public function getFallbackDirs() + { + return $this->fallbackDirsPsr0; + } + + /** + * @return list<string> + */ + public function getFallbackDirsPsr4() + { + return $this->fallbackDirsPsr4; + } + + /** + * @return array<string, string> Array of classname => path + */ + public function getClassMap() + { + return $this->classMap; + } + + /** + * @param array<string, string> $classMap Class to filename map + * + * @return void + */ + public function addClassMap(array $classMap) + { + if ($this->classMap) { + $this->classMap = array_merge($this->classMap, $classMap); + } else { + $this->classMap = $classMap; + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, either + * appending or prepending to the ones previously set for this prefix. + * + * @param string $prefix The prefix + * @param list<string>|string $paths The PSR-0 root directories + * @param bool $prepend Whether to prepend the directories + * + * @return void + */ + public function add($prefix, $paths, $prepend = false) + { + $paths = (array) $paths; + if (!$prefix) { + if ($prepend) { + $this->fallbackDirsPsr0 = array_merge( + $paths, + $this->fallbackDirsPsr0 + ); + } else { + $this->fallbackDirsPsr0 = array_merge( + $this->fallbackDirsPsr0, + $paths + ); + } + + return; + } + + $first = $prefix[0]; + if (!isset($this->prefixesPsr0[$first][$prefix])) { + $this->prefixesPsr0[$first][$prefix] = $paths; + + return; + } + if ($prepend) { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $paths, + $this->prefixesPsr0[$first][$prefix] + ); + } else { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $this->prefixesPsr0[$first][$prefix], + $paths + ); + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, either + * appending or prepending to the ones previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param list<string>|string $paths The PSR-4 base directories + * @param bool $prepend Whether to prepend the directories + * + * @throws \InvalidArgumentException + * + * @return void + */ + public function addPsr4($prefix, $paths, $prepend = false) + { + $paths = (array) $paths; + if (!$prefix) { + // Register directories for the root namespace. + if ($prepend) { + $this->fallbackDirsPsr4 = array_merge( + $paths, + $this->fallbackDirsPsr4 + ); + } else { + $this->fallbackDirsPsr4 = array_merge( + $this->fallbackDirsPsr4, + $paths + ); + } + } elseif (!isset($this->prefixDirsPsr4[$prefix])) { + // Register directories for a new namespace. + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = $paths; + } elseif ($prepend) { + // Prepend directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $paths, + $this->prefixDirsPsr4[$prefix] + ); + } else { + // Append directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $this->prefixDirsPsr4[$prefix], + $paths + ); + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, + * replacing any others previously set for this prefix. + * + * @param string $prefix The prefix + * @param list<string>|string $paths The PSR-0 base directories + * + * @return void + */ + public function set($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr0 = (array) $paths; + } else { + $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, + * replacing any others previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param list<string>|string $paths The PSR-4 base directories + * + * @throws \InvalidArgumentException + * + * @return void + */ + public function setPsr4($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr4 = (array) $paths; + } else { + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } + } + + /** + * Turns on searching the include path for class files. + * + * @param bool $useIncludePath + * + * @return void + */ + public function setUseIncludePath($useIncludePath) + { + $this->useIncludePath = $useIncludePath; + } + + /** + * Can be used to check if the autoloader uses the include path to check + * for classes. + * + * @return bool + */ + public function getUseIncludePath() + { + return $this->useIncludePath; + } + + /** + * Turns off searching the prefix and fallback directories for classes + * that have not been registered with the class map. + * + * @param bool $classMapAuthoritative + * + * @return void + */ + public function setClassMapAuthoritative($classMapAuthoritative) + { + $this->classMapAuthoritative = $classMapAuthoritative; + } + + /** + * Should class lookup fail if not found in the current class map? + * + * @return bool + */ + public function isClassMapAuthoritative() + { + return $this->classMapAuthoritative; + } + + /** + * APCu prefix to use to cache found/not-found classes, if the extension is enabled. + * + * @param string|null $apcuPrefix + * + * @return void + */ + public function setApcuPrefix($apcuPrefix) + { + $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null; + } + + /** + * The APCu prefix in use, or null if APCu caching is not enabled. + * + * @return string|null + */ + public function getApcuPrefix() + { + return $this->apcuPrefix; + } + + /** + * Registers this instance as an autoloader. + * + * @param bool $prepend Whether to prepend the autoloader or not + * + * @return void + */ + public function register($prepend = false) + { + spl_autoload_register(array($this, 'loadClass'), true, $prepend); + + if (null === $this->vendorDir) { + return; + } + + if ($prepend) { + self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders; + } else { + unset(self::$registeredLoaders[$this->vendorDir]); + self::$registeredLoaders[$this->vendorDir] = $this; + } + } + + /** + * Unregisters this instance as an autoloader. + * + * @return void + */ + public function unregister() + { + spl_autoload_unregister(array($this, 'loadClass')); + + if (null !== $this->vendorDir) { + unset(self::$registeredLoaders[$this->vendorDir]); + } + } + + /** + * Loads the given class or interface. + * + * @param string $class The name of the class + * @return true|null True if loaded, null otherwise + */ + public function loadClass($class) + { + if ($file = $this->findFile($class)) { + $includeFile = self::$includeFile; + $includeFile($file); + + return true; + } + + return null; + } + + /** + * Finds the path to the file where the class is defined. + * + * @param string $class The name of the class + * + * @return string|false The path if found, false otherwise + */ + public function findFile($class) + { + // class map lookup + if (isset($this->classMap[$class])) { + return $this->classMap[$class]; + } + if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { + return false; + } + if (null !== $this->apcuPrefix) { + $file = apcu_fetch($this->apcuPrefix.$class, $hit); + if ($hit) { + return $file; + } + } + + $file = $this->findFileWithExtension($class, '.php'); + + // Search for Hack files if we are running on HHVM + if (false === $file && defined('HHVM_VERSION')) { + $file = $this->findFileWithExtension($class, '.hh'); + } + + if (null !== $this->apcuPrefix) { + apcu_add($this->apcuPrefix.$class, $file); + } + + if (false === $file) { + // Remember that this class does not exist. + $this->missingClasses[$class] = true; + } + + return $file; + } + + /** + * Returns the currently registered loaders keyed by their corresponding vendor directories. + * + * @return array<string, self> + */ + public static function getRegisteredLoaders() + { + return self::$registeredLoaders; + } + + /** + * @param string $class + * @param string $ext + * @return string|false + */ + private function findFileWithExtension($class, $ext) + { + // PSR-4 lookup + $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; + + $first = $class[0]; + if (isset($this->prefixLengthsPsr4[$first])) { + $subPath = $class; + while (false !== $lastPos = strrpos($subPath, '\\')) { + $subPath = substr($subPath, 0, $lastPos); + $search = $subPath . '\\'; + if (isset($this->prefixDirsPsr4[$search])) { + $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); + foreach ($this->prefixDirsPsr4[$search] as $dir) { + if (file_exists($file = $dir . $pathEnd)) { + return $file; + } + } + } + } + } + + // PSR-4 fallback dirs + foreach ($this->fallbackDirsPsr4 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { + return $file; + } + } + + // PSR-0 lookup + if (false !== $pos = strrpos($class, '\\')) { + // namespaced class name + $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) + . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); + } else { + // PEAR-like class name + $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; + } + + if (isset($this->prefixesPsr0[$first])) { + foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { + if (0 === strpos($class, $prefix)) { + foreach ($dirs as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + } + } + } + + // PSR-0 fallback dirs + foreach ($this->fallbackDirsPsr0 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + + // PSR-0 include paths. + if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { + return $file; + } + + return false; + } + + /** + * @return void + */ + private static function initializeIncludeClosure() + { + if (self::$includeFile !== null) { + return; + } + + /** + * Scope isolated include. + * + * Prevents access to $this/self from included files. + * + * @param string $file + * @return void + */ + self::$includeFile = \Closure::bind(static function($file) { + include $file; + }, null, null); + } +} diff --git a/vendor/composer/InstalledVersions.php b/vendor/composer/InstalledVersions.php new file mode 100644 index 0000000..51e734a --- /dev/null +++ b/vendor/composer/InstalledVersions.php @@ -0,0 +1,359 @@ +<?php + +/* + * This file is part of Composer. + * + * (c) Nils Adermann <naderman@naderman.de> + * Jordi Boggiano <j.boggiano@seld.be> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer; + +use Composer\Autoload\ClassLoader; +use Composer\Semver\VersionParser; + +/** + * This class is copied in every Composer installed project and available to all + * + * See also https://getcomposer.org/doc/07-runtime.md#installed-versions + * + * To require its presence, you can require `composer-runtime-api ^2.0` + * + * @final + */ +class InstalledVersions +{ + /** + * @var mixed[]|null + * @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}|array{}|null + */ + private static $installed; + + /** + * @var bool|null + */ + private static $canGetVendors; + + /** + * @var array[] + * @psalm-var array<string, array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}> + */ + private static $installedByVendor = array(); + + /** + * Returns a list of all package names which are present, either by being installed, replaced or provided + * + * @return string[] + * @psalm-return list<string> + */ + public static function getInstalledPackages() + { + $packages = array(); + foreach (self::getInstalled() as $installed) { + $packages[] = array_keys($installed['versions']); + } + + if (1 === \count($packages)) { + return $packages[0]; + } + + return array_keys(array_flip(\call_user_func_array('array_merge', $packages))); + } + + /** + * Returns a list of all package names with a specific type e.g. 'library' + * + * @param string $type + * @return string[] + * @psalm-return list<string> + */ + public static function getInstalledPackagesByType($type) + { + $packagesByType = array(); + + foreach (self::getInstalled() as $installed) { + foreach ($installed['versions'] as $name => $package) { + if (isset($package['type']) && $package['type'] === $type) { + $packagesByType[] = $name; + } + } + } + + return $packagesByType; + } + + /** + * Checks whether the given package is installed + * + * This also returns true if the package name is provided or replaced by another package + * + * @param string $packageName + * @param bool $includeDevRequirements + * @return bool + */ + public static function isInstalled($packageName, $includeDevRequirements = true) + { + foreach (self::getInstalled() as $installed) { + if (isset($installed['versions'][$packageName])) { + return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false; + } + } + + return false; + } + + /** + * Checks whether the given package satisfies a version constraint + * + * e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call: + * + * Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3') + * + * @param VersionParser $parser Install composer/semver to have access to this class and functionality + * @param string $packageName + * @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package + * @return bool + */ + public static function satisfies(VersionParser $parser, $packageName, $constraint) + { + $constraint = $parser->parseConstraints((string) $constraint); + $provided = $parser->parseConstraints(self::getVersionRanges($packageName)); + + return $provided->matches($constraint); + } + + /** + * Returns a version constraint representing all the range(s) which are installed for a given package + * + * It is easier to use this via isInstalled() with the $constraint argument if you need to check + * whether a given version of a package is installed, and not just whether it exists + * + * @param string $packageName + * @return string Version constraint usable with composer/semver + */ + public static function getVersionRanges($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + $ranges = array(); + if (isset($installed['versions'][$packageName]['pretty_version'])) { + $ranges[] = $installed['versions'][$packageName]['pretty_version']; + } + if (array_key_exists('aliases', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']); + } + if (array_key_exists('replaced', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']); + } + if (array_key_exists('provided', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']); + } + + return implode(' || ', $ranges); + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present + */ + public static function getVersion($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['version'])) { + return null; + } + + return $installed['versions'][$packageName]['version']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present + */ + public static function getPrettyVersion($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['pretty_version'])) { + return null; + } + + return $installed['versions'][$packageName]['pretty_version']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference + */ + public static function getReference($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['reference'])) { + return null; + } + + return $installed['versions'][$packageName]['reference']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path. + */ + public static function getInstallPath($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @return array + * @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool} + */ + public static function getRootPackage() + { + $installed = self::getInstalled(); + + return $installed[0]['root']; + } + + /** + * Returns the raw installed.php data for custom implementations + * + * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect. + * @return array[] + * @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} + */ + public static function getRawData() + { + @trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED); + + if (null === self::$installed) { + // only require the installed.php file if this file is loaded from its dumped location, + // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 + if (substr(__DIR__, -8, 1) !== 'C') { + self::$installed = include __DIR__ . '/installed.php'; + } else { + self::$installed = array(); + } + } + + return self::$installed; + } + + /** + * Returns the raw data of all installed.php which are currently loaded for custom implementations + * + * @return array[] + * @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}> + */ + public static function getAllRawData() + { + return self::getInstalled(); + } + + /** + * Lets you reload the static array from another file + * + * This is only useful for complex integrations in which a project needs to use + * this class but then also needs to execute another project's autoloader in process, + * and wants to ensure both projects have access to their version of installed.php. + * + * A typical case would be PHPUnit, where it would need to make sure it reads all + * the data it needs from this class, then call reload() with + * `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure + * the project in which it runs can then also use this class safely, without + * interference between PHPUnit's dependencies and the project's dependencies. + * + * @param array[] $data A vendor/composer/installed.php data set + * @return void + * + * @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $data + */ + public static function reload($data) + { + self::$installed = $data; + self::$installedByVendor = array(); + } + + /** + * @return array[] + * @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}> + */ + private static function getInstalled() + { + if (null === self::$canGetVendors) { + self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders'); + } + + $installed = array(); + + if (self::$canGetVendors) { + foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) { + if (isset(self::$installedByVendor[$vendorDir])) { + $installed[] = self::$installedByVendor[$vendorDir]; + } elseif (is_file($vendorDir.'/composer/installed.php')) { + /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */ + $required = require $vendorDir.'/composer/installed.php'; + $installed[] = self::$installedByVendor[$vendorDir] = $required; + if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) { + self::$installed = $installed[count($installed) - 1]; + } + } + } + } + + if (null === self::$installed) { + // only require the installed.php file if this file is loaded from its dumped location, + // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 + if (substr(__DIR__, -8, 1) !== 'C') { + /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */ + $required = require __DIR__ . '/installed.php'; + self::$installed = $required; + } else { + self::$installed = array(); + } + } + + if (self::$installed !== array()) { + $installed[] = self::$installed; + } + + return $installed; + } +} diff --git a/vendor/composer/LICENSE b/vendor/composer/LICENSE new file mode 100644 index 0000000..f27399a --- /dev/null +++ b/vendor/composer/LICENSE @@ -0,0 +1,21 @@ + +Copyright (c) Nils Adermann, Jordi Boggiano + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + diff --git a/vendor/composer/autoload_classmap.php b/vendor/composer/autoload_classmap.php new file mode 100644 index 0000000..0fb0a2c --- /dev/null +++ b/vendor/composer/autoload_classmap.php @@ -0,0 +1,10 @@ +<?php + +// autoload_classmap.php @generated by Composer + +$vendorDir = dirname(__DIR__); +$baseDir = dirname($vendorDir); + +return array( + 'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php', +); diff --git a/vendor/composer/autoload_namespaces.php b/vendor/composer/autoload_namespaces.php new file mode 100644 index 0000000..15a2ff3 --- /dev/null +++ b/vendor/composer/autoload_namespaces.php @@ -0,0 +1,9 @@ +<?php + +// autoload_namespaces.php @generated by Composer + +$vendorDir = dirname(__DIR__); +$baseDir = dirname($vendorDir); + +return array( +); diff --git a/vendor/composer/autoload_psr4.php b/vendor/composer/autoload_psr4.php new file mode 100644 index 0000000..3890ddc --- /dev/null +++ b/vendor/composer/autoload_psr4.php @@ -0,0 +1,9 @@ +<?php + +// autoload_psr4.php @generated by Composer + +$vendorDir = dirname(__DIR__); +$baseDir = dirname($vendorDir); + +return array( +); diff --git a/vendor/composer/autoload_real.php b/vendor/composer/autoload_real.php new file mode 100644 index 0000000..5c6bdc2 --- /dev/null +++ b/vendor/composer/autoload_real.php @@ -0,0 +1,36 @@ +<?php + +// autoload_real.php @generated by Composer + +class ComposerAutoloaderInite2b57849f956d4c67658848cdb027ed8 +{ + private static $loader; + + public static function loadClassLoader($class) + { + if ('Composer\Autoload\ClassLoader' === $class) { + require __DIR__ . '/ClassLoader.php'; + } + } + + /** + * @return \Composer\Autoload\ClassLoader + */ + public static function getLoader() + { + if (null !== self::$loader) { + return self::$loader; + } + + spl_autoload_register(array('ComposerAutoloaderInite2b57849f956d4c67658848cdb027ed8', 'loadClassLoader'), true, true); + self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__)); + spl_autoload_unregister(array('ComposerAutoloaderInite2b57849f956d4c67658848cdb027ed8', 'loadClassLoader')); + + require __DIR__ . '/autoload_static.php'; + call_user_func(\Composer\Autoload\ComposerStaticInite2b57849f956d4c67658848cdb027ed8::getInitializer($loader)); + + $loader->register(true); + + return $loader; + } +} diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php new file mode 100644 index 0000000..3e4da4b --- /dev/null +++ b/vendor/composer/autoload_static.php @@ -0,0 +1,20 @@ +<?php + +// autoload_static.php @generated by Composer + +namespace Composer\Autoload; + +class ComposerStaticInite2b57849f956d4c67658848cdb027ed8 +{ + public static $classMap = array ( + 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', + ); + + public static function getInitializer(ClassLoader $loader) + { + return \Closure::bind(function () use ($loader) { + $loader->classMap = ComposerStaticInite2b57849f956d4c67658848cdb027ed8::$classMap; + + }, null, ClassLoader::class); + } +} diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json new file mode 100644 index 0000000..87fda74 --- /dev/null +++ b/vendor/composer/installed.json @@ -0,0 +1,5 @@ +{ + "packages": [], + "dev": true, + "dev-package-names": [] +} diff --git a/vendor/composer/installed.php b/vendor/composer/installed.php new file mode 100644 index 0000000..61d003e --- /dev/null +++ b/vendor/composer/installed.php @@ -0,0 +1,23 @@ +<?php return array( + 'root' => array( + 'name' => 'drupal/dsfr4drupal', + 'pretty_version' => '1.x-dev', + 'version' => '1.9999999.9999999.9999999-dev', + 'reference' => 'c48cfd677155caefea8b2a0c84ffe527588e0484', + 'type' => 'drupal-theme', + 'install_path' => __DIR__ . '/../../', + 'aliases' => array(), + 'dev' => true, + ), + 'versions' => array( + 'drupal/dsfr4drupal' => array( + 'pretty_version' => '1.x-dev', + 'version' => '1.9999999.9999999.9999999-dev', + 'reference' => 'c48cfd677155caefea8b2a0c84ffe527588e0484', + 'type' => 'drupal-theme', + 'install_path' => __DIR__ . '/../../', + 'aliases' => array(), + 'dev_requirement' => false, + ), + ), +); -- GitLab From 0eccc9430bd0c425ea7454e85d5ce7a1b3549421 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20BRINDLE?= <sebastien.brindle@kleegroup.com> Date: Tue, 18 Mar 2025 16:17:24 +0100 Subject: [PATCH 33/45] Manage base page error 500. --- templates/base/base-page-404.html.twig | 2 +- templates/base/base-page-500.html.twig | 38 ++++++++++++++++++++++++++ translations/fr.po | 15 ++++++++++ 3 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 templates/base/base-page-500.html.twig diff --git a/templates/base/base-page-404.html.twig b/templates/base/base-page-404.html.twig index a0ab027..17535f6 100644 --- a/templates/base/base-page-404.html.twig +++ b/templates/base/base-page-404.html.twig @@ -1,6 +1,6 @@ {% set links = links|default([ { - 'url': url('<front>')|render|render, + 'url': 'internal:/<front>', 'title': 'Homepage'|t, }, ]) %} diff --git a/templates/base/base-page-500.html.twig b/templates/base/base-page-500.html.twig new file mode 100644 index 0000000..4180e5b --- /dev/null +++ b/templates/base/base-page-500.html.twig @@ -0,0 +1,38 @@ +{% set links = links|default([ + { + 'url': 'internal:/contact', + 'title': 'Contact us'|t, + }, +]) %} +{% set subtitle = subtitle|default('Error 500'|t) %} +{% set title = title|default('Unexpected error'|t) %} +{% set text_lead = text_lead|default('Sorry, there is an issue with the service, we are working to resolve it as quickly as possible.'|t) %} +{% set text = text|default('Please try refreshing the page or try again later.'|t) %} + +<div class="fr-py-0 fr-col-12 fr-col-md-6"> + <h1>{{ title }}</h1> + <p class="fr-text--sm fr-mb-3w">{{ subtitle }}</p> + <p class="fr-text--lead fr-mb-3w">{{ text_lead }}</p> + <p class="fr-text--sm fr-mb-5w">{{ text }}</p> + {% if links %} + <ul class="fr-btns-group fr-btns-group--inline-md"> + {% for link in links %} + <li> + {{ link(link.title, link.url, {'class': 'fr-btn'}) }} + </li> + {% endfor %} + </ul> + {{ attach_library('dsfr4drupal/component.button') }} + {% endif %} +</div> +<div class="fr-col-12 fr-col-md-3 fr-col-offset-md-1 fr-px-6w fr-px-md-0 fr-py-0"> + <svg xmlns="http://www.w3.org/2000/svg" class="fr-responsive-img fr-artwork" aria-hidden="true" width="160" height="200" viewBox="0 0 160 200"> + <use class="fr-artwork-motif" href="{{ dsfr_path }}artwork/background/ovoid.svg#artwork-motif"></use> + <use class="fr-artwork-background" href="{{ dsfr_path }}artwork/background/ovoid.svg#artwork-background"></use> + <g transform="translate(40, 60)"> + <use class="fr-artwork-decorative" href="{{ dsfr_path }}artwork/pictograms/system/technical-error.svg#artwork-decorative"></use> + <use class="fr-artwork-minor" href="{{ dsfr_path }}artwork/pictograms/system/technical-error.svg#artwork-minor"></use> + <use class="fr-artwork-major" href="{{ dsfr_path }}artwork/pictograms/system/technical-error.svg#artwork-major"></use> + </g> + </svg> +</div> diff --git a/translations/fr.po b/translations/fr.po index e3ef920..792f25d 100644 --- a/translations/fr.po +++ b/translations/fr.po @@ -268,6 +268,9 @@ msgstr "@code - @label" msgid "Page not found" msgstr "Page non trouvée" +msgid "Unexpected error" +msgstr "Erreur inattendue" + msgid "Show" msgstr "Afficher" @@ -277,9 +280,15 @@ msgstr "Afficher le mot de passe" msgid "Error 404" msgstr "Erreur 404" +msgid "Error 500" +msgstr "Erreur 500" + msgid "The page you are looking for cannot be found. We apologize for the inconvenience caused." msgstr "La page que vous cherchez est introuvable. Excusez-nous pour la gène occasionnée." +msgid "Sorry, there is an issue with the service, we are working to resolve it as quickly as possible." +msgstr "Désolé, le service rencontre un probleÌ€me, nous travaillons pour le reÌsoudre le plus rapidement possible." + msgid "" "If you typed the web address into the browser, verify that it is correct. " "The page may no longer be available.<br />In this case, to continue your visit you can consult our home page, " @@ -289,6 +298,12 @@ msgstr "" "La page n’est peut-être plus disponible.<br />Dans ce cas, pour continuer votre visite vous pouvez consulter notre page d’accueil, " "ou effectuer une recherche avec notre moteur de recherche en haut de page.<br />Sinon contactez-nous pour que l’on puisse vous rediriger vers la bonne information." +msgid "Please try refreshing the page or try again later." +msgstr "Essayez de rafraîchir la page ou bien ressayez plus tard." + +msgid "Contact us" +msgstr "Contactez-nous" + msgid "Copy to clipboard" msgstr "Copier dans le presse papier" -- GitLab From b7bfb94b2483b1437a0393e555bd782ea67313b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20BRINDLE?= <sebastien.brindle@kleegroup.com> Date: Tue, 18 Mar 2025 16:44:34 +0100 Subject: [PATCH 34/45] Manage local action styles. --- css/component/action-links.css | 17 +++++++++++++++++ dsfr4drupal.libraries.yml | 5 +++++ includes/menu.theme | 10 ++++++++++ .../block/block--local-actions-block.html.twig | 10 ++++++++++ templates/menu/menu-local-tasks.html.twig | 2 ++ 5 files changed, 44 insertions(+) create mode 100644 css/component/action-links.css create mode 100644 templates/block/block--local-actions-block.html.twig diff --git a/css/component/action-links.css b/css/component/action-links.css new file mode 100644 index 0000000..3cb17c6 --- /dev/null +++ b/css/component/action-links.css @@ -0,0 +1,17 @@ +/** + * @file + * Styles for local action links. +*/ + +.local-actions { + --local-action-margin: 1rem; + + list-style: none; + margin: 0 calc(var(--local-action-margin) * -1); + padding: 0; +} + +.local-actions li { + display: inline-block; + margin: 0 var(--local-action-margin); +} diff --git a/dsfr4drupal.libraries.yml b/dsfr4drupal.libraries.yml index 3d5621e..9d55cd0 100644 --- a/dsfr4drupal.libraries.yml +++ b/dsfr4drupal.libraries.yml @@ -620,6 +620,11 @@ element.horizontal_tabs: - core/once - dsfr4drupal/component.tab +local-actions: + css: + component: + css/component/action-links.css: {} + #legacy: # js: # /libraries/dsfr/dist/legacy/legacy.nomodule.min.js: diff --git a/includes/menu.theme b/includes/menu.theme index 36748b1..08f0483 100644 --- a/includes/menu.theme +++ b/includes/menu.theme @@ -26,3 +26,13 @@ function dsfr4drupal_preprocess_menu(array &$variables): void { // Define menu "aria-label". $variables['aria_label'] = $menu ? $menu->label() : ''; } + +/** + * Implements hook_preprocess_HOOK() for "menu". + */ +function dsfr4drupal_preprocess_menu_local_action(array &$variables): void { + $link = &$variables['link']; + + $link['#options']['attributes']['class'][] = 'fr-btn'; + $link['#attached']['library'][] = 'dsfr4drupal/component.button'; +} diff --git a/templates/block/block--local-actions-block.html.twig b/templates/block/block--local-actions-block.html.twig new file mode 100644 index 0000000..c117571 --- /dev/null +++ b/templates/block/block--local-actions-block.html.twig @@ -0,0 +1,10 @@ +{% extends 'block.html.twig' %} + +{% block content %} + {% if content %} + <ul class="local-actions"> + {{ content }} + </ul> + {{ attach_library('dsfr4drupal/local-actions') }} + {% endif %} +{% endblock %} diff --git a/templates/menu/menu-local-tasks.html.twig b/templates/menu/menu-local-tasks.html.twig index 90c4ed7..22097ad 100644 --- a/templates/menu/menu-local-tasks.html.twig +++ b/templates/menu/menu-local-tasks.html.twig @@ -1,3 +1,5 @@ +{% set attributes = attributes.addClass('fr-mb-4v') %} + {% if primary %} <h2 class="visually-hidden">{{ 'Primary tabs'|t }}</h2> {{ include('dsfr4drupal:tabs', {'tabs': primary}) }} -- GitLab From 4cc13d2fbca7703f606a231329e83258de6da9b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20BRINDLE?= <sebastien.brindle@kleegroup.com> Date: Wed, 19 Mar 2025 14:17:02 +0100 Subject: [PATCH 35/45] Add missing expanded default value. --- templates/system/details.html.twig | 1 + 1 file changed, 1 insertion(+) diff --git a/templates/system/details.html.twig b/templates/system/details.html.twig index 991c919..f9dce31 100644 --- a/templates/system/details.html.twig +++ b/templates/system/details.html.twig @@ -12,5 +12,6 @@ {{ include('dsfr4drupal:accordion', { 'button_attributes': create_attribute({'class': required ? ['js-form-required', 'form-required'] : ''}), + 'expanded': not element['#attributes']['open'] is empty, 'title_tag': 'div', }) }} -- GitLab From 0a3d88e2041e2c744ae4926c642bb5eb52069fb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20BRINDLE?= <sebastien.brindle@kleegroup.com> Date: Wed, 19 Mar 2025 14:26:59 +0100 Subject: [PATCH 36/45] Manage dropbutton. --- css/component/checkbox.css | 4 +- css/component/dropbutton.css | 34 ++++++++++ css/component/table.css | 14 ++++ css/theme/media-library.css | 5 ++ dsfr4drupal.info.yml | 2 + dsfr4drupal.libraries.yml | 11 ++++ js/dropbutton.js | 76 ++++++++++++++++++++++ templates/form/links--dropbutton.html.twig | 63 ++++++++++++++++++ 8 files changed, 207 insertions(+), 2 deletions(-) create mode 100644 css/component/dropbutton.css create mode 100644 css/component/table.css create mode 100644 js/dropbutton.js create mode 100644 templates/form/links--dropbutton.html.twig diff --git a/css/component/checkbox.css b/css/component/checkbox.css index 412003e..462f787 100644 --- a/css/component/checkbox.css +++ b/css/component/checkbox.css @@ -7,8 +7,8 @@ .fr-checkbox-group input[type="checkbox"] + label.visually-hidden { width: 1.5rem; height: 1.5rem; - margin-left: .5rem; - margin-top: .5rem; + margin-left: 0; + margin-top: 0; padding-left: 1.5rem; clip: auto; z-index: 1; diff --git a/css/component/dropbutton.css b/css/component/dropbutton.css new file mode 100644 index 0000000..1ac8298 --- /dev/null +++ b/css/component/dropbutton.css @@ -0,0 +1,34 @@ +/** + * @file + * Manage styles for dropbutton. + */ + +html.js .dropbutton-wrapper .dropbutton .secondary-action { + display: block; +} + +.dropbutton-wrapper:not(.open) .dropbutton__item:first-of-type ~ .dropbutton__item { + visibility: hidden; + /** + * By setting a height of 1px, the dropbutton items are hidden + * from view while still occupying minimal space, ensuring the layout remains intact. + */ + height: 1px; +} + +.dropbutton__items { + position: fixed; + padding: 0; + background-color: var(--background-default-grey); +} +.dropbutton__items a { + margin-right: 2em; +} + +.dropbutton li { + padding-bottom: 0; +} + +.js td .dropbutton-multiple { + padding-right: 0; +} diff --git a/css/component/table.css b/css/component/table.css new file mode 100644 index 0000000..962213b --- /dev/null +++ b/css/component/table.css @@ -0,0 +1,14 @@ +/** + * @file + * Manage styles for table. + */ + +.fr-table__content td .fr-checkbox-group, +.fr-table__content th .fr-checkbox-group { + vertical-align: top; +} + +.fr-table__content th.select-all input[type="checkbox"] { + width: 1.5rem; + height: 1.5rem; +} diff --git a/css/theme/media-library.css b/css/theme/media-library.css index e5d4c7b..772f706 100644 --- a/css/theme/media-library.css +++ b/css/theme/media-library.css @@ -36,3 +36,8 @@ display: inline-block; font-weight: 700; } + +.views-field-media-library-select-form .fr-checkbox-group input[type="checkbox"] + label.visually-hidden { + margin-left: .5rem; + margin-top: .5rem; +} diff --git a/dsfr4drupal.info.yml b/dsfr4drupal.info.yml index a563b75..bce1acd 100644 --- a/dsfr4drupal.info.yml +++ b/dsfr4drupal.info.yml @@ -38,6 +38,8 @@ libraries: libraries-extend: core/drupal.dialog: - dsfr4drupal/drupal.dialog + core/drupal.dropbutton: + - dsfr4drupal/drupal.dropbutton core/drupal.form: - dsfr4drupal/drupal.form core/drupal.message: diff --git a/dsfr4drupal.libraries.yml b/dsfr4drupal.libraries.yml index 9d55cd0..dc72f62 100644 --- a/dsfr4drupal.libraries.yml +++ b/dsfr4drupal.libraries.yml @@ -401,6 +401,7 @@ component.table: css: component: /libraries/dsfr/dist/component/table/table.min.css: { minified: true } + css/component/table.css: {} js: /libraries/dsfr/dist/component/table/table.module.min.js: minified: true @@ -558,6 +559,16 @@ drupal.dialog: # Need to fix weight to 99 to override "Gin toolbar" styles. css/theme/ui-dialog.css: { weight: 100 } +drupal.dropbutton: + css: + component: + css/component/dropbutton.css: {} + js: + js/dropbutton.js: {} + dependencies: + - core/drupal + - core/once + drupal.form: css: component: diff --git a/js/dropbutton.js b/js/dropbutton.js new file mode 100644 index 0000000..403cddd --- /dev/null +++ b/js/dropbutton.js @@ -0,0 +1,76 @@ +/** + * @file + * Manage "dropbutton" behaviors. + */ + +((Drupal, once) => { + Drupal.behaviors.dsfr4drupalDropbutton = { + attach: function (context) { + once("dsfr4drupal-dropbutton", ".dropbutton-multiple:has(.dropbutton--dsfr4drupal)", context).forEach(element => { + element.querySelector(".dropbutton-toggle").addEventListener("click", () => { + this.updatePosition(element); + + window.addEventListener("scroll", () => Drupal.debounce(this.updatePositionIfOpen(element), 100)); + window.addEventListener("resize", () => Drupal.debounce(this.updatePositionIfOpen(element), 100)); + }); + }); + }, + + updatePosition: (element) => { + const preferredDir = document.documentElement.dir ?? "ltr"; + const secondaryAction = element.querySelector(".secondary-action"); + const dropMenu = secondaryAction.querySelector(".dropbutton__items"); + const toggleHeight = element.offsetHeight; + const dropMenuWidth = dropMenu.offsetWidth; + const dropMenuHeight = dropMenu.offsetHeight; + const boundingRect = secondaryAction.getBoundingClientRect(); + const spaceBelow = window.innerHeight - boundingRect.bottom; + const spaceLeft = boundingRect.left; + const spaceRight = window.innerWidth - boundingRect.right; + + // Calculate the menu position based on available space and the preferred + // reading direction. + const leftAlignStyles = { + left: `${boundingRect.left}px`, + right: "auto" + }; + const rightAlignStyles = { + left: "auto", + right: `${window.innerWidth - boundingRect.right}px` + }; + + if (preferredDir === "ltr") { + if (spaceRight >= dropMenuWidth) { + Object.assign(dropMenu.style, leftAlignStyles); + } + else { + Object.assign(dropMenu.style, rightAlignStyles); + } + } + else { + if (spaceLeft >= dropMenuWidth) { + Object.assign(dropMenu.style, rightAlignStyles); + } + else { + Object.assign(dropMenu.style, leftAlignStyles); + } + } + + if (spaceBelow >= dropMenuHeight) { + dropMenu.style.top = `${boundingRect.bottom}px`; + } + else { + dropMenu.style.top = `${boundingRect.top - toggleHeight - dropMenuHeight}px` + } + + }, + + updatePositionIfOpen: (element) => { + if (element.classList.contains("open")) { + this.updatePosition(element); + } + }, + + }; + +})(Drupal, once); diff --git a/templates/form/links--dropbutton.html.twig b/templates/form/links--dropbutton.html.twig new file mode 100644 index 0000000..0b94975 --- /dev/null +++ b/templates/form/links--dropbutton.html.twig @@ -0,0 +1,63 @@ +{# +/** + * @file + * Theme override for a set of links. + * + * Available variables: + * - attributes: Attributes for the UL containing the list of links. + * - links: Links to be output. + * Each link will have the following elements: + * - link: (optional) A render array that returns a link. See + * template_preprocess_links() for details how it is generated. + * - text: The link text. + * - attributes: HTML attributes for the list item element. + * - text_attributes: (optional) HTML attributes for the span element if no + * 'url' was supplied. + * - heading: (optional) A heading to precede the links. + * - text: The heading text. + * - level: The heading level (e.g. 'h2', 'h3'). + * - attributes: (optional) A keyed list of attributes for the heading. + * If the heading is a string, it will be used as the text of the heading and + * the level will default to 'h2'. + * + * Headings should be used on navigation menus and any list of links that + * consistently appears on multiple pages. To make the heading invisible use + * the 'visually-hidden' CSS class. Do not use 'display:none', which + * removes it from screen readers and assistive technology. Headings allow + * screen reader and keyboard only users to navigate to or skip the links. + * See http://juicystudio.com/article/screen-readers-display-none.php and + * http://www.w3.org/TR/WCAG-TECHS/H42.html for more information. + * + * @see template_preprocess_links() + */ +#} +{% if links -%} + {%- if heading -%} + {%- if heading.level -%} + <{{ heading.level }}{{ heading.attributes }}>{{ heading.text }}</{{ heading.level }}> + {%- else -%} + <h2{{ heading.attributes }}>{{ heading.text }}</h2> + {%- endif -%} + {%- endif -%} + <ul{{ attributes.addClass('dropbutton--dsfr4drupal') }}> + {%- for item in links -%} + {% if loop.index == 2 %} + <li class="dropbutton__item"> + <ul class="dropbutton__items"> + {% endif %} + <li{{ item.attributes.addClass('dropbutton__item') }}> + {%- if item.link -%} + {{ item.link }} + {%- elseif item.text_attributes -%} + <span{{ item.text_attributes }}>{{ item.text }}</span> + {%- else -%} + {{ item.text }} + {%- endif -%} + </li> + {% if loop.length > 1 and loop.last %} + </ul> + </li> + {% endif %} + {%- endfor -%} + </ul> +{%- endif %} -- GitLab From 58260cf983f8d5bef77d504c13beb993dc100245 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20BRINDLE?= <sebastien.brindle@kleegroup.com> Date: Wed, 19 Mar 2025 16:37:41 +0100 Subject: [PATCH 37/45] Stylize system admin pages. --- css/theme/system.admin.css | 23 +++++++++++++++++++ dsfr4drupal.info.yml | 2 ++ dsfr4drupal.libraries.yml | 5 ++++ .../system/admin-block-content.html.twig | 14 +++++++++++ templates/system/admin-page.html.twig | 13 +++++++++++ 5 files changed, 57 insertions(+) create mode 100644 css/theme/system.admin.css create mode 100644 templates/system/admin-block-content.html.twig create mode 100644 templates/system/admin-page.html.twig diff --git a/css/theme/system.admin.css b/css/theme/system.admin.css new file mode 100644 index 0000000..510c76b --- /dev/null +++ b/css/theme/system.admin.css @@ -0,0 +1,23 @@ +/** + * @file + * Manage styles for system admin. + */ + +.compact-link { + text-align: right; +} + +.panel { + padding: 1rem 1rem .5rem; + border: 1px solid var(--border-default-grey); +} + +.panel + .panel { + margin-top: 1.5rem; +} + +.list-group dd + dt { + margin-top: .25rem; + padding-top: .5rem; + border-top: 1px solid var(--border-default-grey); +} diff --git a/dsfr4drupal.info.yml b/dsfr4drupal.info.yml index bce1acd..84f10ba 100644 --- a/dsfr4drupal.info.yml +++ b/dsfr4drupal.info.yml @@ -66,6 +66,8 @@ libraries-extend: - dsfr4drupal/drupal.paragraphs.widget select2/select2: - dsfr4drupal/select2 + system/admin: + - dsfr4drupal/admin tarte_au_citron/tarte_au_citron_lib: - dsfr4drupal/tarteaucitron tacjs/tarteaucitron.js: diff --git a/dsfr4drupal.libraries.yml b/dsfr4drupal.libraries.yml index dc72f62..7d080f5 100644 --- a/dsfr4drupal.libraries.yml +++ b/dsfr4drupal.libraries.yml @@ -2,6 +2,11 @@ # These Javascript files are needed for old/outdated browsers. # @see: https://www.systeme-de-design.gouv.fr/utilisation-et-organisation/developpeurs/prise-en-main/ +admin: + css: + theme: + css/theme/system.admin.css: {} + component.accordion: css: component: diff --git a/templates/system/admin-block-content.html.twig b/templates/system/admin-block-content.html.twig new file mode 100644 index 0000000..c3f4433 --- /dev/null +++ b/templates/system/admin-block-content.html.twig @@ -0,0 +1,14 @@ +{% set classes = [ + 'list-group', + compact ? 'compact', +] %} +{% if content %} + <dl{{ attributes.addClass(classes) }}> + {% for item in content %} + <dt class="list-group__link fr-mt-2v">{{ item.link }}</dt> + {% if item.description %} + <dd class="list-group__description">{{ item.description }}</dd> + {% endif %} + {% endfor %} + </dl> +{% endif %} diff --git a/templates/system/admin-page.html.twig b/templates/system/admin-page.html.twig new file mode 100644 index 0000000..b82b265 --- /dev/null +++ b/templates/system/admin-page.html.twig @@ -0,0 +1,13 @@ +<div class="clearfix"> + {{ system_compact_link|add_class('fr-text--sm') }} + + <div class="fr-grid-row fr-grid-row--gutters"> + {% for container in containers %} + <div class="fr-col-12 fr-col-md-6"> + {% for block in container.blocks %} + {{ block }} + {% endfor %} + </div> + {% endfor %} + </div> +</div> -- GitLab From aa3951df9f1c6144b71522761c8144b93ea94b34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20BRINDLE?= <sebastien.brindle@kleegroup.com> Date: Wed, 19 Mar 2025 17:12:00 +0100 Subject: [PATCH 38/45] Fix checkbox group spacing. --- css/component/dropbutton.css | 6 +- css/component/form.css | 17 ++++++ dsfr4drupal.info.yml | 2 - dsfr4drupal.libraries.yml | 6 +- .../status-report-general-info.html.twig | 61 +++++++++++++++++++ 5 files changed, 84 insertions(+), 8 deletions(-) create mode 100644 templates/system/status-report-general-info.html.twig diff --git a/css/component/dropbutton.css b/css/component/dropbutton.css index 1ac8298..eb147fd 100644 --- a/css/component/dropbutton.css +++ b/css/component/dropbutton.css @@ -18,7 +18,7 @@ html.js .dropbutton-wrapper .dropbutton .secondary-action { .dropbutton__items { position: fixed; - padding: 0; + padding: .25rem; background-color: var(--background-default-grey); } .dropbutton__items a { @@ -32,3 +32,7 @@ html.js .dropbutton-wrapper .dropbutton .secondary-action { .js td .dropbutton-multiple { padding-right: 0; } + +.js .dropbutton li, .js .dropbutton a { + display: inline-block; +} diff --git a/css/component/form.css b/css/component/form.css index 7ccda7c..a94449c 100644 --- a/css/component/form.css +++ b/css/component/form.css @@ -26,6 +26,18 @@ --form-spacing: 0; } +/*Remove specific spacing into fieldset - START */ +.fr-fieldset__content .fr-checkbox-group input[type="checkbox"] + label::before, +.fr-fieldset__content .fr-radio-group:not(.fr-radio-rich) input[type="radio"] + label::before { + top: auto; +} + +.fr-fieldset__content .fr-checkbox-group label, +.fr-fieldset__content .fr-radio-group label { + padding: 0; +} +/*Remove specific spacing into fieldset - END */ + main[role="main"] > .fr-container > form, main[role="main"] > .fr-container--fluid > form { margin-block: var(--form-spacing); @@ -35,6 +47,11 @@ main[role="main"] > .fr-container--fluid > form { margin-block: var(--form-item-spacing); } +.fr-checkbox-group + .fr-checkbox-group, +.fr-radio-group + .fr-radio-group { + margin-top: .25rem; +} + .form-wrapper.js-filter-wrapper { font-size: .75em; } diff --git a/dsfr4drupal.info.yml b/dsfr4drupal.info.yml index 84f10ba..3c2d105 100644 --- a/dsfr4drupal.info.yml +++ b/dsfr4drupal.info.yml @@ -40,8 +40,6 @@ libraries-extend: - dsfr4drupal/drupal.dialog core/drupal.dropbutton: - dsfr4drupal/drupal.dropbutton - core/drupal.form: - - dsfr4drupal/drupal.form core/drupal.message: - dsfr4drupal/drupal.message core/drupal.tabledrag: diff --git a/dsfr4drupal.libraries.yml b/dsfr4drupal.libraries.yml index 7d080f5..dbb187b 100644 --- a/dsfr4drupal.libraries.yml +++ b/dsfr4drupal.libraries.yml @@ -148,6 +148,7 @@ component.form: css: component: /libraries/dsfr/dist/component/form/form.min.css: { minified: true } + css/component/form.css: {} dependencies: - dsfr4drupal/core @@ -574,11 +575,6 @@ drupal.dropbutton: - core/drupal - core/once -drupal.form: - css: - component: - css/component/form.css: {} - drupal.layout_builder: css: theme: diff --git a/templates/system/status-report-general-info.html.twig b/templates/system/status-report-general-info.html.twig new file mode 100644 index 0000000..c1554f7 --- /dev/null +++ b/templates/system/status-report-general-info.html.twig @@ -0,0 +1,61 @@ +<h2>{{ 'General System Information'|t }}</h2> +<div class="fr-grid-row fr-grid-row--gutters fr-mb-4v"> + <div class="fr-col-12 fr-col-md-6"> + <div class="system-status-general-info__item"> + <h3 class="fr-h5 system-status-general-info__item-title">{{ 'Drupal Version'|t }}</h3> + {{ drupal.value }} + {% if drupal.description %} + {{ drupal.description }} + {% endif %} + </div> + </div> + <div class="fr-col-12 fr-col-md-6"> + <div class="system-status-general-info__item"> + <h3 class="fr-h5 system-status-general-info__item-title">{{ 'Web Server'|t }}</h3> + {{ webserver.value }} + {% if webserver.description %} + {{ webserver.description }} + {% endif %} + </div> + </div> + <div class="fr-col-12 fr-col-md-6"> + <div class="system-status-general-info__item"> + <h3 class="fr-h5 system-status-general-info__item-title">{{ 'PHP'|t }}</h3> + <h4 class="fr-h6">{{ 'Version'|t }}</h4> {{ php.value }} + {% if php.description %} + {{ php.description }} + {% endif %} + + <h4 class="fr-h6">{{ 'Memory limit'|t }}</h4>{{ php_memory_limit.value }} + {% if php_memory_limit.description %} + {{ php_memory_limit.description }} + {% endif %} + </div> + </div> + <div class="fr-col-12 fr-col-md-6"> + <div class="system-status-general-info__item"> + <h3 class="fr-h5 system-status-general-info__item-title">{{ 'Database'|t }}</h3> + <h4 class="fr-h6">{{ 'Version'|t }}</h4>{{ database_system_version.value }} + {% if database_system_version.description %} + {{ database_system_version.description }} + {% endif %} + + <h4 class="fr-h6">{{ 'System'|t }}</h4>{{ database_system.value }} + {% if database_system.description %} + {{ database_system.description }} + {% endif %} + </div> + </div> + <div class="fr-col-12"> + <div class="system-status-general-info__item"> + <h3 class="fr-h5 system-status-general-info__item-title">{{ 'Last Cron Run'|t }}</h3> + {{ cron.value }} + {% if cron.run_cron %} + {{ cron.run_cron }} + {% endif %} + {% if cron.description %} + {{ cron.description }} + {% endif %} + </div> + </div> +</div> -- GitLab From ea149ee5cf2e54d94985fae6011e42a891f6e77c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20BRINDLE?= <sebastien.brindle@kleegroup.com> Date: Wed, 19 Mar 2025 17:43:28 +0100 Subject: [PATCH 39/45] Fix indent. --- src/Dsfr4DrupalPreRender.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Dsfr4DrupalPreRender.php b/src/Dsfr4DrupalPreRender.php index 0674233..cbea266 100644 --- a/src/Dsfr4DrupalPreRender.php +++ b/src/Dsfr4DrupalPreRender.php @@ -19,7 +19,7 @@ class Dsfr4DrupalPreRender implements TrustedCallbackInterface { * The render array element. * * @return array - * The new render array element. + * The new render array element. */ public static function horizontalTabs(array $element): array { return self::tabs($element, 'horizontal'); @@ -29,10 +29,10 @@ class Dsfr4DrupalPreRender implements TrustedCallbackInterface { * Prerender callback for Vertical Tabs element. * * @param array $element - * The render array element. + * The render array element. * * @return array - * The new render array element. + * The new render array element. */ public static function verticalTabs(array $element): array { return self::tabs($element, 'vertical'); @@ -42,10 +42,10 @@ class Dsfr4DrupalPreRender implements TrustedCallbackInterface { * Prerender callback for tabs element. * * @param array $element - * The render array element. + * The render array element. * * @return array - * The new render array element. + * The new render array element. */ private static function tabs(array $element, string $orientation): array { $isDetails = isset($element['group']['#type']) && $element['group']['#type'] === 'details'; -- GitLab From 0e12abcf7154834140c01f3e7f2af6fcb92d0e72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20BRINDLE?= <sebastien.brindle@kleegroup.com> Date: Wed, 19 Mar 2025 18:12:14 +0100 Subject: [PATCH 40/45] Stylize media grid page. --- css/theme/media-library.css | 35 ++++++++++++++++++++++++++++++----- dsfr4drupal.info.yml | 2 ++ dsfr4drupal.libraries.yml | 9 ++++++++- includes/views.theme | 31 +++++++++++++++++++++++++++++++ js/media-library.view.js | 24 ++++++++++++++++++++++++ 5 files changed, 95 insertions(+), 6 deletions(-) create mode 100644 js/media-library.view.js diff --git a/css/theme/media-library.css b/css/theme/media-library.css index 772f706..a31b7ac 100644 --- a/css/theme/media-library.css +++ b/css/theme/media-library.css @@ -8,14 +8,21 @@ } /* Remove margin at the end of the form */ -.media-library-view .views-form { +.js-media-library-view .views-form { margin-bottom: calc(var(--form-spacing) * -1); } -.js-media-library-item { +.js-media-library-view .views-row { + position: relative; margin-block: var(--form-item-spacing) !important; /* stylelint-disable-line declaration-no-important */ } +.js-media-library-view .views-row .fr-checkbox-group { + position: absolute; + margin-top: .5rem; + margin-left: .5rem; +} + .js-media-library-add-form-added-media { --ul-type: none; } @@ -37,7 +44,25 @@ font-weight: 700; } -.views-field-media-library-select-form .fr-checkbox-group input[type="checkbox"] + label.visually-hidden { - margin-left: .5rem; - margin-top: .5rem; +.media-library-select-all input[type="checkbox"] { + display: inline-block; + vertical-align: bottom; + width: 1.5rem; + height: 1.5rem; + margin-right: .5rem; +} + +.media-library-item__edit, +.media-library-item__remove { + position: absolute; + z-index: 1; + top: 1.25rem; +} + +.media-library-item__edit { + right: 3.5rem; +} + +.media-library-item__remove { + right: 1.25rem; } diff --git a/dsfr4drupal.info.yml b/dsfr4drupal.info.yml index 3c2d105..caf13fb 100644 --- a/dsfr4drupal.info.yml +++ b/dsfr4drupal.info.yml @@ -33,6 +33,7 @@ ckeditor5-stylesheets: - css/ckeditor5.css libraries: + - dsfr4drupal/core - dsfr4drupal/utility libraries-extend: @@ -52,6 +53,7 @@ libraries-extend: - dsfr4drupal/drupal.layout_builder media_library/view: - dsfr4drupal/media_library.theme + - dsfr4drupal/media_library.view media_library/widget: - dsfr4drupal/media_library.theme navigation/navigation.layout: diff --git a/dsfr4drupal.libraries.yml b/dsfr4drupal.libraries.yml index dbb187b..cb88047 100644 --- a/dsfr4drupal.libraries.yml +++ b/dsfr4drupal.libraries.yml @@ -121,6 +121,8 @@ component.display.button: css: theme: css/theme/display.button.css: {} + dependencies: + - dsfr4drupal/core component.connect: css: @@ -650,8 +652,13 @@ media_library.theme: css: theme: css/theme/media-library.css: {} + +media_library.view: + js: + js/media-library.view.js: {} dependencies: - - dsfr4drupal/core + - core/drupal + - core/once navigation.layout: css: diff --git a/includes/views.theme b/includes/views.theme index 176e8f4..cd4484d 100644 --- a/includes/views.theme +++ b/includes/views.theme @@ -6,6 +6,7 @@ */ declare(strict_types=1); +use Drupal\views\ViewExecutable; /** * Implements hook_preprocss_hook() for "views_view__media_library". @@ -33,3 +34,33 @@ function dsfr4drupal_preprocess_views_view__media_library(array &$variables): vo $variables['#attached']['library'][] = 'dsfr4drupal/component.tab'; $variables['#attached']['library'][] = 'dsfr4drupal/component.link'; } + +/** + * Implements hook_views_pre_render(). + */ +function dsfr4drupal_views_pre_render(ViewExecutable $view) { + if ( + $view->id() === 'media_library' && + $view->current_display === 'page' + ) { + $add_classes = function (&$option, array $classes_to_add) { + $classes = preg_split('/\s+/', $option); + $classes = array_filter($classes); + $classes = array_merge($classes, $classes_to_add); + $option = implode(' ', array_unique($classes)); + }; + + if (array_key_exists('edit_media', $view->field)) { + $add_classes( + $view->field['edit_media']->options['alter']['link_class'], + ['media-library-item__edit', 'fr-btn', 'fr-btn--secondary', 'fr-btn--sm', 'fr-icon-ball-pen-fill'] + ); + } + if (array_key_exists('delete_media', $view->field)) { + $add_classes( + $view->field['delete_media']->options['alter']['link_class'], + ['media-library-item__remove', 'fr-btn', 'fr-btn--secondary', 'fr-btn--sm', 'fr-icon-close-line'] + ); + } + } +} diff --git a/js/media-library.view.js b/js/media-library.view.js new file mode 100644 index 0000000..e360d7d --- /dev/null +++ b/js/media-library.view.js @@ -0,0 +1,24 @@ +/** + * @file + * Manage media library view behaviors. + */ + +((Drupal, once) => { + "use strict"; + + + Drupal.behaviors.dsfrMediaLibraryView = { + attach: (context) => { + const selectAll = once("dsfr-media-library-select-all", ".media-library-select-all", context); + if (!selectAll.length) { + return; + } + + selectAll.forEach(element => { + // Move "select all" element after header. + context.querySelector("#edit-header").after(element); + }); + }, + }; + +})(Drupal, once); -- GitLab From 66650cd0d81b54cb75a86f11121423b7e86a3416 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20BRINDLE?= <sebastien.brindle@kleegroup.com> Date: Wed, 19 Mar 2025 20:07:03 +0100 Subject: [PATCH 41/45] Fix linter. --- css/component/dropbutton.css | 1 + css/component/form.css | 60 ++++++++++++++++++------------------ 2 files changed, 31 insertions(+), 30 deletions(-) diff --git a/css/component/dropbutton.css b/css/component/dropbutton.css index eb147fd..fbf4881 100644 --- a/css/component/dropbutton.css +++ b/css/component/dropbutton.css @@ -21,6 +21,7 @@ html.js .dropbutton-wrapper .dropbutton .secondary-action { padding: .25rem; background-color: var(--background-default-grey); } + .dropbutton__items a { margin-right: 2em; } diff --git a/css/component/form.css b/css/component/form.css index a94449c..e8222dd 100644 --- a/css/component/form.css +++ b/css/component/form.css @@ -26,7 +26,35 @@ --form-spacing: 0; } -/*Remove specific spacing into fieldset - START */ +.fr-checkbox-group + .fr-checkbox-group, +.fr-radio-group + .fr-radio-group { + margin-top: .25rem; +} + +.form-actions a + button, +.form-actions button + button, +.form-actions button + a { + margin-left: var(--form-item-spacing); +} + +.fr-label > .field-edit-link { + font-size: .75em; +} + +.fr-label .field-edit-link > button { + padding: 0; +} + +.fr-upload-group { + padding: var(--form-spacing-xs) var(--form-spacing) var(--form-spacing); + border: 1px solid var(--border-default-grey); +} + +.fr-upload-group > label { + font-weight: 700; +} + +/* Remove specific spacing into fieldset - START */ .fr-fieldset__content .fr-checkbox-group input[type="checkbox"] + label::before, .fr-fieldset__content .fr-radio-group:not(.fr-radio-rich) input[type="radio"] + label::before { top: auto; @@ -36,7 +64,7 @@ .fr-fieldset__content .fr-radio-group label { padding: 0; } -/*Remove specific spacing into fieldset - END */ +/* Remove specific spacing into fieldset - END */ main[role="main"] > .fr-container > form, main[role="main"] > .fr-container--fluid > form { @@ -47,21 +75,10 @@ main[role="main"] > .fr-container--fluid > form { margin-block: var(--form-item-spacing); } -.fr-checkbox-group + .fr-checkbox-group, -.fr-radio-group + .fr-radio-group { - margin-top: .25rem; -} - .form-wrapper.js-filter-wrapper { font-size: .75em; } -.form-actions a + button, -.form-actions button + button, -.form-actions button + a { - margin-left: var(--form-item-spacing); -} - /* Disable table scrolling into form item - START */ .form-item .fr-table__container { overflow: initial; @@ -77,23 +94,6 @@ main[role="main"] > .fr-container--fluid > form { } /* Disable table scrolling into form - END */ -.fr-label > .field-edit-link { - font-size: .75em; -} - -.fr-label .field-edit-link > button { - padding: 0; -} - -.fr-upload-group { - padding: var(--form-spacing-xs) var(--form-spacing) var(--form-spacing); - border: 1px solid var(--border-default-grey); -} - -.fr-upload-group > label { - font-weight: 700; -} - .js-filter-wrapper { margin-top: calc(var(--form-item-spacing) * -.75); } -- GitLab From 19d2bb1773ad9fdd1a3c348a0b4037c24179163b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20BRINDLE?= <sebastien.brindle@kleegroup.com> Date: Thu, 20 Mar 2025 14:45:01 +0100 Subject: [PATCH 42/45] Limit views exposed form styles to admin pages and some other views. --- css/component/form.css | 6 ++++++ css/component/views-exposed-form.css | 2 -- includes/form.theme | 22 ++++++++++++++++++++++ 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/css/component/form.css b/css/component/form.css index e8222dd..46e53f3 100644 --- a/css/component/form.css +++ b/css/component/form.css @@ -26,6 +26,12 @@ --form-spacing: 0; } +/* Provide a class to wrap form with borders. */ +.dsfr4drupal-form-bordered { + padding: var(--form-spacing-xs) var(--form-spacing-s) var(--form-spacing-s); + border: 1px solid var(--border-default-grey); +} + .fr-checkbox-group + .fr-checkbox-group, .fr-radio-group + .fr-radio-group { margin-top: .25rem; diff --git a/css/component/views-exposed-form.css b/css/component/views-exposed-form.css index 3ebbefc..fa7ba22 100644 --- a/css/component/views-exposed-form.css +++ b/css/component/views-exposed-form.css @@ -10,8 +10,6 @@ display: flex; flex-wrap: wrap; margin-block: var(--form-spacing-s); - padding: var(--form-spacing-xs) var(--form-spacing-s) var(--form-spacing-s); - border: 1px solid var(--border-default-grey); } .views-exposed-form--preview.views-exposed-form--preview { diff --git a/includes/form.theme b/includes/form.theme index db0eeb8..151c71a 100644 --- a/includes/form.theme +++ b/includes/form.theme @@ -34,6 +34,28 @@ function dsfr4drupal_form_node_preview_form_select_alter(array &$form, FormState $form['#attached']['library'][] = 'dsfr4drupal/component.link'; } +/** + * Implements hook_form_FORM_ID_alter() for "views_exposed_form". + */ +function dsfr4drupal_form_views_exposed_form_alter(array &$form, FormStateInterface $form_state, string $form_id): void { + /** @var \Drupal\views\ViewExecutable $view */ + $view = $form_state->getStorage()['view']; + + $view_ids = [ + // Stylize media library widget modal. + 'media_library', + ]; + + if ( + // Stylize views exposed form on admin pages. + \Drupal::service('router.admin_context')->isAdminRoute() || + // Specific rule for some views. + in_array($view->id(), $view_ids) + ) { + $form['#attributes']['class'][] = 'dsfr4drupal-form-bordered'; + } +} + /** * Implements hook_preprocess_HOOK() for "fieldset". */ -- GitLab From 57e5ac78f326370ac49f7e10661ac09202b156ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20BRINDLE?= <sebastien.brindle@kleegroup.com> Date: Thu, 20 Mar 2025 15:45:53 +0100 Subject: [PATCH 43/45] Finalize rendering. --- css/component/form.css | 6 +++--- css/component/radio.css | 12 ++++++++++++ css/component/views-exposed-form.css | 16 ++++++++-------- dsfr4drupal.libraries.yml | 1 + includes/form.theme | 3 ++- .../block/block--local-actions-block.html.twig | 2 +- 6 files changed, 27 insertions(+), 13 deletions(-) create mode 100644 css/component/radio.css diff --git a/css/component/form.css b/css/component/form.css index 46e53f3..e8c9651 100644 --- a/css/component/form.css +++ b/css/component/form.css @@ -27,7 +27,7 @@ } /* Provide a class to wrap form with borders. */ -.dsfr4drupal-form-bordered { +.dsfr4drupal-form--bordered { padding: var(--form-spacing-xs) var(--form-spacing-s) var(--form-spacing-s); border: 1px solid var(--border-default-grey); } @@ -77,8 +77,8 @@ main[role="main"] > .fr-container--fluid > form { margin-block: var(--form-spacing); } -.form-wrapper { - margin-block: var(--form-item-spacing); +.form-wrapper:not(:last-child) { + margin-bottom: var(--form-item-spacing); } .form-wrapper.js-filter-wrapper { diff --git a/css/component/radio.css b/css/component/radio.css new file mode 100644 index 0000000..ffc4840 --- /dev/null +++ b/css/component/radio.css @@ -0,0 +1,12 @@ +/** + * @file + * Manage styles for radio. + */ + +.fr-fieldset .fr-fieldset__content .fr-radio-group:not(.fr-radio-rich) input[type="radio"] { + top: auto; +} + +.fr-fieldset .fr-fieldset__content .fr-radio-group:not(.fr-radio-rich) input[type="radio"] + label { + background-position: left center; +} diff --git a/css/component/views-exposed-form.css b/css/component/views-exposed-form.css index fa7ba22..263b11a 100644 --- a/css/component/views-exposed-form.css +++ b/css/component/views-exposed-form.css @@ -6,7 +6,7 @@ /** * Use flexbox and some margin resets to make the fields + actions go inline. */ -.views-exposed-form { +.views-exposed-form--inline { display: flex; flex-wrap: wrap; margin-block: var(--form-spacing-s); @@ -16,24 +16,24 @@ margin-top: 0; } -.views-exposed-form__item { +.views-exposed-form--inline .views-exposed-form__item { max-width: 100%; margin-block: var(--form-spacing-s) 0; margin-inline: 0 var(--form-spacing-xs); } -.views-exposed-form .form-item--no-label, -.views-exposed-form__item.views-exposed-form__item.views-exposed-form__item--actions { +.views-exposed-form--inline .form-item--no-label, +.views-exposed-form--inline .views-exposed-form__item.views-exposed-form__item.views-exposed-form__item--actions { margin-block: var(--form-spacing-s) 0; align-self: flex-end; } -.views-exposed-form .form-item--no-label, -.views-exposed-form__item.views-exposed-form__item--actions { +.views-exposed-form--inline .form-item--no-label, +.views-exposed-form--inline .views-exposed-form__item.views-exposed-form__item--actions { margin-top: calc(var(--form-label-line-height) + var(--form-label-input-spacing)); } -.views-exposed-form .fr-input-group:not(:last-child), -.views-exposed-form .fr-select-group:not(:last-child) { +.views-exposed-form--inline .fr-input-group:not(:last-child), +.views-exposed-form--inline .fr-select-group:not(:last-child) { margin-bottom: 0; } diff --git a/dsfr4drupal.libraries.yml b/dsfr4drupal.libraries.yml index cb88047..57f6416 100644 --- a/dsfr4drupal.libraries.yml +++ b/dsfr4drupal.libraries.yml @@ -313,6 +313,7 @@ component.radio: css: component: /libraries/dsfr/dist/component/radio/radio.min.css: { minified: true } + css/component/radio.css: {} dependencies: - dsfr4drupal/component.form - dsfr4drupal/core diff --git a/includes/form.theme b/includes/form.theme index 151c71a..f7582d4 100644 --- a/includes/form.theme +++ b/includes/form.theme @@ -52,7 +52,8 @@ function dsfr4drupal_form_views_exposed_form_alter(array &$form, FormStateInterf // Specific rule for some views. in_array($view->id(), $view_ids) ) { - $form['#attributes']['class'][] = 'dsfr4drupal-form-bordered'; + $form['#attributes']['class'][] = 'views-exposed-form--inline'; + $form['#attributes']['class'][] = 'dsfr4drupal-form--bordered'; } } diff --git a/templates/block/block--local-actions-block.html.twig b/templates/block/block--local-actions-block.html.twig index c117571..04c8bbe 100644 --- a/templates/block/block--local-actions-block.html.twig +++ b/templates/block/block--local-actions-block.html.twig @@ -2,7 +2,7 @@ {% block content %} {% if content %} - <ul class="local-actions"> + <ul class="local-actions fr-mb-4v"> {{ content }} </ul> {{ attach_library('dsfr4drupal/local-actions') }} -- GitLab From 7c61f8bdff8988f4855eb89d1977f7a789c8b7e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20BRINDLE?= <sebastien.brindle@kleegroup.com> Date: Thu, 20 Mar 2025 15:49:00 +0100 Subject: [PATCH 44/45] Manage help text as tooltip in fieldset. --- includes/form.theme | 3 +++ templates/form/fieldset.html.twig | 9 ++++++++- templates/form/form-element-label.html.twig | 3 +-- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/includes/form.theme b/includes/form.theme index f7582d4..7f73ee6 100644 --- a/includes/form.theme +++ b/includes/form.theme @@ -75,6 +75,9 @@ function dsfr4drupal_preprocess_fieldset(array &$variables): void { $variables['attributes']['aria-labelledby'] .= ' ' . $variables['error_id']; } + // Show help text as tooltip. + $variables['description_tooltip'] = theme_get_setting('form_description_tooltip') ?? FALSE; + $variables['#attached']['library'][] = 'dsfr4drupal/component.form'; } diff --git a/templates/form/fieldset.html.twig b/templates/form/fieldset.html.twig index 2143e15..a7bdb64 100644 --- a/templates/form/fieldset.html.twig +++ b/templates/form/fieldset.html.twig @@ -14,7 +14,14 @@ <fieldset{{ attributes.setAttribute('role', 'group').addClass(classes) }}> <legend{{ legend.attributes.addClass(legend_classes) }}>{{ legend.title }} {% if description.content %} - <span class="fr-hint-text">{{ description.content }}</span> + {% if description_tooltip %} + {{ include('dsfr4drupal:tooltip', { + 'title': tooltip_title|default('Help text'|t), + 'tooltip': description.content, + }, with_context=false) }} + {% else %} + <span class="fr-hint-text">{{ description.content }}</span> + {% endif %} {% endif %} </legend> <div class="fr-fieldset__content"> diff --git a/templates/form/form-element-label.html.twig b/templates/form/form-element-label.html.twig index a8138ad..0686a5c 100644 --- a/templates/form/form-element-label.html.twig +++ b/templates/form/form-element-label.html.twig @@ -1,4 +1,3 @@ -{% set tooltip_title = tooltip_title|default('Help text'|t) %} {% set classes = [ title_display == 'after' ? 'option', title_display == 'invisible' ? 'visually-hidden', @@ -11,7 +10,7 @@ {% if description.content %} {% if description_tooltip %} {{ include('dsfr4drupal:tooltip', { - 'title': tooltip_title, + 'title': tooltip_title|default('Help text'|t), 'tooltip': description.content, }, with_context=false) }} {% else %} -- GitLab From e1b90b9845eb1d0334c54fa61ff5d5f76e1d79fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20BRINDLE?= <sebastien.brindle@kleegroup.com> Date: Fri, 21 Mar 2025 12:04:56 +0100 Subject: [PATCH 45/45] Remove useless "role" attribute. --- templates/form/fieldset.html.twig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/form/fieldset.html.twig b/templates/form/fieldset.html.twig index a7bdb64..1584727 100644 --- a/templates/form/fieldset.html.twig +++ b/templates/form/fieldset.html.twig @@ -11,7 +11,7 @@ required ? 'form-required', ] %} <div class="fr-form-group"> - <fieldset{{ attributes.setAttribute('role', 'group').addClass(classes) }}> + <fieldset{{ attributes.addClass(classes) }}> <legend{{ legend.attributes.addClass(legend_classes) }}>{{ legend.title }} {% if description.content %} {% if description_tooltip %} -- GitLab