From a902834ced186897d62fc3a0af4c60dc7ec4de80 Mon Sep 17 00:00:00 2001 From: Cristina Chumillas <7898-ckrina@users.noreply.drupalcode.org> Date: Wed, 16 Apr 2025 12:09:44 +0200 Subject: [PATCH] Issue #3516887 by plopesc, gxleano, m4olivei, pdureau: Allow to customize Navigation icons --- .../toolbar-button.component.yml | 18 ++++- .../toolbar-button/toolbar-button.twig | 6 +- .../navigation/layouts/navigation.html.twig | 6 +- .../src/Menu/NavigationMenuLinkTree.php | 14 +++- .../src/Plugin/Block/NavigationLinkBlock.php | 3 + .../Plugin/Block/NavigationShortcutsBlock.php | 3 + .../src/Plugin/TopBarItem/PageActions.php | 4 +- .../navigation/src/ShortcutLazyBuilder.php | 3 + .../templates/navigation-menu.html.twig | 4 +- .../templates/top-bar-page-actions.html.twig | 2 +- .../assets/icons/radioactive.svg | 1 + .../navigation_test/assets/icons/star.svg | 3 + .../navigation_test/navigation_test.icons.yml | 36 ++++++++++ .../navigation_test.links.menu.yml | 37 +++++++++++ .../src/Hook/NavigationTestHooks.php | 17 +++++ .../src/Plugin/Block/NavigationTestBlock.php | 3 + .../src/Functional/NavigationIconTest.php | 66 +++++++++++++++++++ 17 files changed, 214 insertions(+), 12 deletions(-) create mode 100644 core/modules/navigation/tests/navigation_test/assets/icons/radioactive.svg create mode 100644 core/modules/navigation/tests/navigation_test/assets/icons/star.svg create mode 100644 core/modules/navigation/tests/navigation_test/navigation_test.icons.yml create mode 100644 core/modules/navigation/tests/navigation_test/navigation_test.links.menu.yml create mode 100644 core/modules/navigation/tests/src/Functional/NavigationIconTest.php diff --git a/core/modules/navigation/components/toolbar-button/toolbar-button.component.yml b/core/modules/navigation/components/toolbar-button/toolbar-button.component.yml index 0a7e9853f5cc..87e29a14bf44 100644 --- a/core/modules/navigation/components/toolbar-button/toolbar-button.component.yml +++ b/core/modules/navigation/components/toolbar-button/toolbar-button.component.yml @@ -57,7 +57,23 @@ props: default: button icon: title: Icon - type: string + type: object + properties: + pack_id: + title: Icon Pack + type: string + default: navigation + icon_id: + title: Icon ID + type: string + settings: + title: Icon Settings + type: object + default: + class: toolbar-button__icon + size: 20 + required: + - icon_id text: title: Text description: Text of button. diff --git a/core/modules/navigation/components/toolbar-button/toolbar-button.twig b/core/modules/navigation/components/toolbar-button/toolbar-button.twig index a4e061986ac8..c220d1008a6a 100644 --- a/core/modules/navigation/components/toolbar-button/toolbar-button.twig +++ b/core/modules/navigation/components/toolbar-button/toolbar-button.twig @@ -3,7 +3,7 @@ appear after main classes #} {% set classes = [ 'toolbar-button', - icon ? 'toolbar-button--icon--' ~ icon : '', + icon.icon_id ? 'toolbar-button--icon--' ~ icon.icon_id : '', ] %} @@ -24,8 +24,8 @@ appear after main classes #} <{{ html_tag|default('button') }} {{ attributes.addClass(classes) }}> - {% if icon %} - {{ icon('navigation', icon, { class: 'toolbar-button__icon', size: 20 }) }} + {% if icon.icon_id %} + {{ icon(icon.pack_id|default('navigation'), icon.icon_id, icon.settings|default({ class: 'toolbar-button__icon', size: 20 })) }} {% endif %} {% if action %} diff --git a/core/modules/navigation/layouts/navigation.html.twig b/core/modules/navigation/layouts/navigation.html.twig index 699d14f72c02..6f9804e332dd 100644 --- a/core/modules/navigation/layouts/navigation.html.twig +++ b/core/modules/navigation/layouts/navigation.html.twig @@ -22,7 +22,7 @@ <div class="admin-toolbar-control-bar__content"> {% include 'navigation:toolbar-button' with { attributes: create_attribute({'aria-expanded': 'false', 'aria-controls': 'admin-toolbar', 'type': 'button'}), - icon: 'burger', + icon: { icon_id: 'burger' }, text: 'Expand sidebar'|t, modifiers: ['small-offset'], extra_classes: [ @@ -59,14 +59,14 @@ {% include 'navigation:toolbar-button' with { attributes: create_attribute({ 'data-toolbar-back-control': true, 'tabindex': '-1' }), extra_classes: ['admin-toolbar__back-button'], - icon: 'arrow-left', + icon: { icon_id: 'arrow-left' }, text: 'Back'|t, } only %} {% include 'navigation:toolbar-button' with { action: 'Collapse sidebar'|t, attributes: create_attribute({ 'aria-controls': 'admin-toolbar', 'tabindex': '-1', 'type': 'button' }), extra_classes: ['admin-toolbar__close-button'], - icon: 'cross', + icon: { icon_id: 'cross' }, } only %} </div> diff --git a/core/modules/navigation/src/Menu/NavigationMenuLinkTree.php b/core/modules/navigation/src/Menu/NavigationMenuLinkTree.php index ca45eb581d94..68ce42fc02b3 100644 --- a/core/modules/navigation/src/Menu/NavigationMenuLinkTree.php +++ b/core/modules/navigation/src/Menu/NavigationMenuLinkTree.php @@ -4,6 +4,8 @@ namespace Drupal\navigation\Menu; +use Drupal\Component\Utility\Html; +use Drupal\Component\Utility\NestedArray; use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Menu\MenuActiveTrailInterface; use Drupal\Core\Menu\MenuLinkManagerInterface; @@ -72,8 +74,18 @@ public function build(array $tree): array { foreach ($tree as $item) { if ($item->access->isAllowed()) { $plugin_id = $item->link->getPluginId(); - $plugin_class = str_replace('.', '_', $plugin_id); + $plugin_class = Html::getClass(str_replace('.', '_', $plugin_id)); $build['#items'][$plugin_id]['class'] = $plugin_class; + $url = $build['#items'][$plugin_id]['url']; + $icon_defaults = [ + 'pack_id' => 'navigation', + 'icon_id' => $plugin_class, + 'settings' => [ + 'class' => 'toolbar-button__icon', + 'size' => 20, + ], + ]; + $build['#items'][$plugin_id]['icon'] = NestedArray::mergeDeep($icon_defaults, $url->getOption('icon') ?? []); } } diff --git a/core/modules/navigation/src/Plugin/Block/NavigationLinkBlock.php b/core/modules/navigation/src/Plugin/Block/NavigationLinkBlock.php index 7a73de500d8e..2f96c7615bfd 100644 --- a/core/modules/navigation/src/Plugin/Block/NavigationLinkBlock.php +++ b/core/modules/navigation/src/Plugin/Block/NavigationLinkBlock.php @@ -274,6 +274,9 @@ public function build(): array { 'title' => $config['title'], 'class' => $config['icon_class'], 'url' => $url, + 'icon' => [ + 'icon_id' => $config['icon_class'], + ], ], ], ]; diff --git a/core/modules/navigation/src/Plugin/Block/NavigationShortcutsBlock.php b/core/modules/navigation/src/Plugin/Block/NavigationShortcutsBlock.php index e5e2f7f4526d..a74ac0c472e4 100644 --- a/core/modules/navigation/src/Plugin/Block/NavigationShortcutsBlock.php +++ b/core/modules/navigation/src/Plugin/Block/NavigationShortcutsBlock.php @@ -89,6 +89,9 @@ public function build(): array { [ 'title' => $this->configuration['label'], 'class' => 'shortcuts', + 'icon' => [ + 'icon_id' => 'shortcuts', + ], ], ], ], diff --git a/core/modules/navigation/src/Plugin/TopBarItem/PageActions.php b/core/modules/navigation/src/Plugin/TopBarItem/PageActions.php index c759676bc9e7..eb9e88015d72 100644 --- a/core/modules/navigation/src/Plugin/TopBarItem/PageActions.php +++ b/core/modules/navigation/src/Plugin/TopBarItem/PageActions.php @@ -119,7 +119,9 @@ protected function getFeaturedPageActions(array $page_actions): ?array { if (isset($page_actions['page_actions'][$edit_route]) && $page_actions['page_actions'][$edit_route]['#access']?->isAllowed()) { $featured_page_actions[$edit_route] = [ 'page_action' => $page_actions['page_actions'][$edit_route], - 'icon' => 'pencil', + 'icon' => [ + 'icon_id' => 'pencil', + ], ]; } } diff --git a/core/modules/navigation/src/ShortcutLazyBuilder.php b/core/modules/navigation/src/ShortcutLazyBuilder.php index a229c2674cee..121c29f7e865 100644 --- a/core/modules/navigation/src/ShortcutLazyBuilder.php +++ b/core/modules/navigation/src/ShortcutLazyBuilder.php @@ -53,6 +53,9 @@ public function lazyLinks(string $label = 'Shortcuts') { [ 'title' => $label, 'class' => 'shortcuts', + 'icon' => [ + 'icon_id' => 'shortcuts', + ], 'below' => $shortcut_links['shortcuts']['#links'], ], ]; diff --git a/core/modules/navigation/templates/navigation-menu.html.twig b/core/modules/navigation/templates/navigation-menu.html.twig index 48de2f9604e2..f5b905caf1af 100644 --- a/core/modules/navigation/templates/navigation-menu.html.twig +++ b/core/modules/navigation/templates/navigation-menu.html.twig @@ -31,7 +31,7 @@ <li id="{{ item_id }}" class="toolbar-block__list-item"> {% include 'navigation:toolbar-button' with { attributes: item_link_attributes.setAttribute('href', item.url|render|default(null)).setAttribute('data-drupal-tooltip', item.title).setAttribute('data-drupal-tooltip-class', 'admin-toolbar__tooltip'), - icon: item.class|clean_class, + icon: item.icon, html_tag: item_link_tag, text: item.title, modifiers: [ @@ -50,7 +50,7 @@ 'data-toolbar-popover-control': true, 'data-has-safe-triangle': true, }), - icon: item.class|clean_class, + icon: item.icon, text: item.title, modifiers: [ 'expand--side', diff --git a/core/modules/navigation/templates/top-bar-page-actions.html.twig b/core/modules/navigation/templates/top-bar-page-actions.html.twig index 2fb8edce6100..b412e3353b36 100644 --- a/core/modules/navigation/templates/top-bar-page-actions.html.twig +++ b/core/modules/navigation/templates/top-bar-page-actions.html.twig @@ -22,7 +22,7 @@ {% endfor %} {% include 'navigation:toolbar-button' with { - icon: 'dots', + icon: { icon_id: 'dots' }, action: 'More actions'|t, attributes: create_attribute( { diff --git a/core/modules/navigation/tests/navigation_test/assets/icons/radioactive.svg b/core/modules/navigation/tests/navigation_test/assets/icons/radioactive.svg new file mode 100644 index 000000000000..466281e1cc09 --- /dev/null +++ b/core/modules/navigation/tests/navigation_test/assets/icons/radioactive.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="#000000" viewBox="0 0 256 256"><path d="M92,136H40a16,16,0,0,1-11.76-5.21,16.21,16.21,0,0,1-4.17-12.37A103.83,103.83,0,0,1,67.65,42.93,16,16,0,0,1,90.75,48l26,45a8,8,0,1,1-13.86,8L76.89,56A87.83,87.83,0,0,0,40,119.86a.19.19,0,0,0,.07.16L92,120a8,8,0,0,1,0,16Zm139.93-17.58a103.83,103.83,0,0,0-43.58-75.49A16,16,0,0,0,165.25,48L139.3,93a8,8,0,0,0,13.86,8l26-45A87.87,87.87,0,0,1,216,119.86c0,.07,0,.12,0,.14H164a8,8,0,0,0,0,16h52a16,16,0,0,0,11.76-5.21A16.21,16.21,0,0,0,231.93,118.42Zm-79,36.76a8,8,0,1,0-13.86,8l25.84,44.73a88.22,88.22,0,0,1-73.81,0l25.83-44.73a8,8,0,1,0-13.86-8L77.25,199.91a16,16,0,0,0,7.12,22.52,104.24,104.24,0,0,0,87.26,0,16,16,0,0,0,7.12-22.52ZM128,140a12,12,0,1,0-12-12A12,12,0,0,0,128,140Z"></path></svg> \ No newline at end of file diff --git a/core/modules/navigation/tests/navigation_test/assets/icons/star.svg b/core/modules/navigation/tests/navigation_test/assets/icons/star.svg new file mode 100644 index 000000000000..093da7c72f46 --- /dev/null +++ b/core/modules/navigation/tests/navigation_test/assets/icons/star.svg @@ -0,0 +1,3 @@ +<svg width="22" height="21" viewBox="0 0 22 21" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path d="M21.4251 8.121C21.3342 7.84104 21.1631 7.594 20.9328 7.41063C20.7026 7.22726 20.4236 7.11566 20.1304 7.08975L14.5626 6.60974L12.3801 1.41974C12.2664 1.14742 12.0748 0.914795 11.8292 0.751174C11.5836 0.587552 11.2952 0.500244 11.0001 0.500244C10.705 0.500244 10.4165 0.587552 10.1709 0.751174C9.92537 0.914795 9.7337 1.14742 9.62007 1.41974L7.44413 6.60974L1.86976 7.09256C1.57542 7.11729 1.29493 7.22838 1.06347 7.41188C0.832008 7.59539 0.65988 7.84315 0.568668 8.1241C0.477457 8.40504 0.471222 8.70666 0.550747 8.99113C0.630272 9.2756 0.792015 9.53027 1.01569 9.72318L5.24476 13.4188L3.97726 18.9069C3.91023 19.1941 3.92936 19.4947 4.03224 19.7711C4.13512 20.0475 4.31719 20.2874 4.55569 20.4609C4.79419 20.6344 5.07853 20.7337 5.37317 20.7464C5.66781 20.7592 5.95967 20.6848 6.21226 20.5326L10.9935 17.6263L15.7851 20.5326C16.0377 20.6848 16.3295 20.7592 16.6242 20.7464C16.9188 20.7337 17.2031 20.6344 17.4416 20.4609C17.6801 20.2874 17.8622 20.0475 17.9651 19.7711C18.068 19.4947 18.0871 19.1941 18.0201 18.9069L16.7535 13.4132L20.9816 9.72318C21.2053 9.5296 21.3667 9.27421 21.4456 8.98914C21.5245 8.70406 21.5174 8.40202 21.4251 8.121ZM19.9982 8.58975L15.7701 12.2797C15.5643 12.4587 15.4112 12.6905 15.3273 12.95C15.2434 13.2095 15.2318 13.487 15.2938 13.7526L16.5641 19.2501L11.7763 16.3438C11.5427 16.2016 11.2745 16.1263 11.001 16.1263C10.7275 16.1263 10.4593 16.2016 10.2257 16.3438L5.44444 19.2501L6.70632 13.7563C6.76834 13.4907 6.75676 13.2132 6.67285 12.9537C6.58893 12.6942 6.43585 12.4625 6.23007 12.2835L2.00007 8.59537C1.99973 8.59257 1.99973 8.58973 2.00007 8.58693L7.57257 8.10506C7.84463 8.08108 8.10499 7.98327 8.32555 7.82219C8.54611 7.6611 8.7185 7.44286 8.82413 7.191L11.0001 2.00756L13.1751 7.191C13.2807 7.44286 13.4531 7.6611 13.6737 7.82219C13.8942 7.98327 14.1546 8.08108 14.4266 8.10506L20.0001 8.58693C20.0001 8.58693 20.0001 8.59256 20.0001 8.59349L19.9982 8.58975Z" fill="currentColor"/> +</svg> diff --git a/core/modules/navigation/tests/navigation_test/navigation_test.icons.yml b/core/modules/navigation/tests/navigation_test/navigation_test.icons.yml new file mode 100644 index 000000000000..832c2ec5b777 --- /dev/null +++ b/core/modules/navigation/tests/navigation_test/navigation_test.icons.yml @@ -0,0 +1,36 @@ +navigation_test: + enabled: true + label: "Drupal Navigation Test" + description: "Icons available within Drupal Navigation Test module." + version: 11.x + license: + name: GPL2-or-later + url: https://api.drupal.org/api/drupal/core%21LICENSE.txt/11.x + gpl-compatible: true + extractor: svg + config: + sources: + - assets/icons/*.svg + settings: + size: + title: "Size" + description: "Set a size for this icon." + type: "integer" + default: 20 + class: + title: "Class" + description: "Set a class for this icon." + type: "string" + default: "" + template: > + <svg + {{ attributes + .setAttribute('viewBox', attributes.viewBox|default('0 0 24 24')) + .setAttribute('class', class) + .setAttribute('width', size|default('20')) + .setAttribute('height', size|default('20')) + .setAttribute('aria-hidden', 'true') + }} + > + {{ content }} + </svg> diff --git a/core/modules/navigation/tests/navigation_test/navigation_test.links.menu.yml b/core/modules/navigation/tests/navigation_test/navigation_test.links.menu.yml new file mode 100644 index 000000000000..7c6eb33ded87 --- /dev/null +++ b/core/modules/navigation/tests/navigation_test/navigation_test.links.menu.yml @@ -0,0 +1,37 @@ +navigation_test.navigation__custom_item: + title: "Test Custom Icon" + weight: 0 + menu_name: admin + parent: system.admin + route_name: "<front>" + options: + icon: + pack_id: navigation_test + icon_id: star + settings: + class: 'toolbar-button__icon foo' + size: 25 + +navigation_test.navigation__default_item: + title: "Test Default Icon" + weight: 1 + menu_name: admin + parent: system.admin + route_name: "<front>" + options: + icon: + icon_id: pencil + +navigation_test.navigation__no_icon: + title: "Test No Icon" + weight: 2 + menu_name: admin + parent: system.admin + route_name: "<front>" + +navigation.media: + title: "Test Default Logic Icon" + weight: 3 + menu_name: admin + parent: system.admin + route_name: "<front>" diff --git a/core/modules/navigation/tests/navigation_test/src/Hook/NavigationTestHooks.php b/core/modules/navigation/tests/navigation_test/src/Hook/NavigationTestHooks.php index d0c4006e6662..b61a694e80f4 100644 --- a/core/modules/navigation/tests/navigation_test/src/Hook/NavigationTestHooks.php +++ b/core/modules/navigation/tests/navigation_test/src/Hook/NavigationTestHooks.php @@ -104,4 +104,21 @@ public function navigationMenuLinkTreeAlter(array &$tree): void { } } + /** + * Implements hook_menu_links_discovered_alter(). + */ + #[Hook('menu_links_discovered_alter')] + public function menuLinksDiscoveredAlter(array &$links): void { + if (\Drupal::keyValue('navigation_test')->get('menu_links_discovered_alter')) { + $links['navigation_test.navigation__no_icon']['options']['icon'] = [ + 'icon_id' => 'radioactive', + 'pack_id' => 'navigation_test', + ]; + $links['navigation_test.navigation__default_item']['options']['icon'] = [ + 'icon_id' => 'foo', + 'pack_id' => 'bar', + ]; + } + } + } diff --git a/core/modules/navigation/tests/navigation_test_block/src/Plugin/Block/NavigationTestBlock.php b/core/modules/navigation/tests/navigation_test_block/src/Plugin/Block/NavigationTestBlock.php index eb34725d1900..05d1f1b55053 100644 --- a/core/modules/navigation/tests/navigation_test_block/src/Plugin/Block/NavigationTestBlock.php +++ b/core/modules/navigation/tests/navigation_test_block/src/Plugin/Block/NavigationTestBlock.php @@ -35,6 +35,9 @@ public function build(): array { [ 'title' => 'Test Navigation Block', 'class' => 'test-block', + 'icon' => [ + 'icon_id' => 'test-block', + ], 'url' => Url::fromRoute('<front>'), ], ], diff --git a/core/modules/navigation/tests/src/Functional/NavigationIconTest.php b/core/modules/navigation/tests/src/Functional/NavigationIconTest.php new file mode 100644 index 000000000000..e4cd508bb67b --- /dev/null +++ b/core/modules/navigation/tests/src/Functional/NavigationIconTest.php @@ -0,0 +1,66 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\Tests\navigation\Functional; + +use Drupal\Core\Menu\MenuLinkManagerInterface; +use Drupal\Tests\BrowserTestBase; + +/** + * Tests Navigation Icon behavior. + * + * @group navigation + */ +class NavigationIconTest extends BrowserTestBase { + + /** + * {@inheritdoc} + */ + protected static $modules = ['navigation', 'navigation_test', 'test_page_test']; + + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + + /** + * {@inheritdoc} + */ + protected function setUp(): void { + parent::setUp(); + + $this->drupalLogin($this->createUser([ + 'access navigation', + ])); + } + + /** + * Tests the behavior of custom icons. + */ + public function testNavigationIcon(): void { + $this->drupalGet('/test-page'); + $this->assertSession()->elementAttributeContains('css', 'a.toolbar-button--icon--star svg', 'width', '25'); + $this->assertSession()->elementAttributeContains('css', 'a.toolbar-button--icon--star svg', 'class', 'toolbar-button__icon foo'); + $this->assertSession()->elementAttributeContains('css', 'a.toolbar-button--icon--pencil svg', 'width', '20'); + $this->assertSession()->elementAttributeContains('css', 'a.toolbar-button--icon--pencil svg', 'class', 'toolbar-button__icon'); + $this->assertSession()->elementAttributeContains('css', 'a.toolbar-button--icon--navigation-media svg', 'width', '20'); + $this->assertSession()->elementAttributeContains('css', 'a.toolbar-button--icon--navigation-media svg', 'class', 'toolbar-button__icon'); + $this->assertSession()->elementExists('css', 'a.toolbar-button--icon--navigation-test-navigation__no-icon'); + $this->assertSession()->elementNotExists('css', 'a.toolbar-button--icon--navigation-test-navigation__no-icon svg'); + + // Rebuild menu with alterations and reload the page to check them. + \Drupal::keyValue('navigation_test')->set('menu_links_discovered_alter', 1); + \Drupal::service(MenuLinkManagerInterface::class)->rebuild(); + + $this->drupalGet('/test-page'); + $this->assertSession()->elementAttributeContains('css', 'a.toolbar-button--icon--star svg', 'width', '25'); + $this->assertSession()->elementAttributeContains('css', 'a.toolbar-button--icon--star svg', 'class', 'toolbar-button__icon foo'); + $this->assertSession()->elementNotExists('css', 'a.toolbar-button--icon--pencil svg'); + $this->assertSession()->elementAttributeContains('css', 'a.toolbar-button--icon--navigation-media svg', 'width', '20'); + $this->assertSession()->elementAttributeContains('css', 'a.toolbar-button--icon--navigation-media svg', 'class', 'toolbar-button__icon'); + $this->assertSession()->elementAttributeContains('css', 'a.toolbar-button--icon--radioactive svg', 'width', '20'); + $this->assertSession()->elementAttributeContains('css', 'a.toolbar-button--icon--radioactive svg', 'class', 'toolbar-button__icon'); + } + +} -- GitLab