Le nouveau module Navigation du core (la sidebar verticale qui remplace la vieille toolbar horizontale) affiche une icône devant chaque entrée de premier niveau.
Pour les entrées du core, tout est prévu. Mais dès qu'un module custom ajoute son propre lien de niveau 1 (une section « métier », un raccourci « Vider le cache »...), la sidebar se rabat sur un pis-aller : les deux premières lettres du titre dans un carré. Fonctionnel, mais pas très flatteur.
Nous allons voir comment avoir des éléments de menus custom avec leurs icônes :
Comment que ça marche dans le core ?
Au moment de construire l'arbre de la sidebar, NavigationMenuLinkTree fabrique pour chaque lien de premier niveau un render array d'icône avec des valeurs par défaut, puis fusionne par-dessus l'option icon du lien si elle existe :
// web/core/modules/navigation/src/Menu/NavigationMenuLinkTree.php (extrait simplifié)
$icon_defaults = [
'pack_id' => 'navigation',
'icon_id' => $plugin_class,
'settings' => [
'class' => 'toolbar-button__icon',
'size' => 20,
],
];
$icon = NestedArray::mergeDeep($icon_defaults, $url->getOption('icon') ?? []);Trois choses à retenir de ces quelques lignes :
- par défaut, l'icône est cherchée dans le pack
navigationdu core, avec un identifiant dérivé du plugin id du lien. C'est comme ça que les entrées du core ont leurs icônes... et que les nôtres n'en ont pas : aucun SVG du pack core ne s'appelle comme notre lien, d'où le repli sur les initiales ; - un lien de menu peut fournir sa propre définition via
$url->getOption('icon'): pack, icône, et même les settings ; - le core transmet deux settings au pack : une classe CSS (
toolbar-button__icon) et une taille (20). Nous allons y revenir.
Déclarer l'icône sur le lien de menu
Les « options » d'un lien de menu se déclarent directement dans le *.links.menu.yml du module. Il suffit donc d'y poser une clé options.icon qui pointe vers n'importe quel pack d'icônes installé, celui du projet comme une bibliothèque tierce :
# mon_module.links.menu.yml
mon_module.admin:
title: 'Popote Minute'
route_name: mon_module.admin
parent: system.admin
weight: -15
options:
icon:
pack_id: mon_pack
icon_id: toque
mon_module.flush_caches:
title: 'Vider le cache'
route_name: mon_module.flush_caches
parent: system.admin
weight: -16
options:
icon:
pack_id: iconoir
icon_id: trashIci la toque vient du pack maison du projet et la poubelle de la bibliothèque Iconoir. La création de ces packs (fichier *.icons.yml, normalisation des SVG en currentColor, settings...) est détaillée dans l'article précédent sur l'API Icon et UI Icons ; celui-ci en est la suite directe.
Un drush cr plus tard (les définitions de liens de menu sont en cache, comme le reste), les icônes apparaissent dans la sidebar.
Le réglage class du pack
Petit détail qui a son importance : le core transmet settings: ['class' => 'toolbar-button__icon', 'size' => 20] au pack. Cette classe porte le dimensionnement et l'alignement de l'icône dans le bouton de la sidebar. Si le template du pack l'ignore, l'icône sort du gabarit.
Un pack maison doit donc déclarer un setting class et le reporter sur la racine du SVG :
settings:
class:
title: 'Class'
description: 'Classes CSS supplémentaires, transmises notamment par le module Navigation.'
type: 'string'Et dans le template du pack :
.addClass('icon', 'icon--' ~ icon_id|clean_class, class|default(''))Pour récapituler, voici l'intégralité de mon fichier mon_theme.icons.yaml
mon_pack:
enabled: true
label: 'Popote Icons'
description: 'Icônes SVG du projet Popote Minute.'
version: '1.0.0'
license:
name: 'Proprietary'
url: 'https://popote-minute.fr'
gpl-compatible: false
extractor: svg
config:
sources:
- icons/*.svg
settings:
size:
title: 'Size'
description: "Largeur et hauteur en pixels. Sans valeur, le CSS du contexte décide."
type: 'integer'
color:
title: 'Color'
description: "Couleur de l'icône (remplissage ou trait selon l'icône). Défaut : couleur du texte."
type: 'string'
format: 'color'
stroke:
title: 'Stroke width'
description: "Épaisseur du trait des icônes filaires (arrow, chevron…)."
type: 'number'
default: 2
minimum: 1
maximum: 3
multipleOf: 0.1
class:
title: 'Class'
description: "Classe(s) CSS ajoutée(s) au SVG — le core Navigation s'en sert (toolbar-button__icon)."
type: 'string'
template: >-
{% if color %}<span style="color: {{ color }};">{% endif %}
<svg{{
attributes
.setAttribute('xmlns', 'http://www.w3.org/2000/svg')
.setAttribute('viewBox', attributes.viewBox|default('0 0 24 24'))
.setAttribute('stroke-width', stroke|default(2))
.setAttribute('aria-hidden', 'true')
.setAttribute('focusable', 'false')
.addClass('icon', 'icon--' ~ icon_id|clean_class, class|default(''))
}}{% if size %} width="{{ size }}" height="{{ size }}"{% endif %}>{{ content }}</svg>
{% if color %}</span>{% endif %}
iconoir:
enabled: true
label: "Iconoir"
description: "Iconoir is an open-source library with 1500+ unique SVG icons, designed on a 24x24 pixels grid."
links:
- https://github.com/iconoir-icons/iconoir
version: 7.11.0
license:
name: MIT
url: https://github.com/iconoir-icons/iconoir/blob/main/LICENSE
gpl-compatible: true
extractor: svg
config:
sources:
- /libraries/node_modules/iconoir/icons/{group}/{icon_id}.svg
settings:
size:
title: "Size"
type: "integer"
default: 32
color:
title: "Color"
type: "string"
format: "color"
stroke:
title: "Stroke width"
type: "number"
default: 1.5
minimum: 1.0
maximum: 2.0
multipleOf: 0.1
class:
title: "Class"
description: "Classe(s) CSS ajoutée(s) au SVG — le core Navigation s'en sert (toolbar-button__icon)."
type: "string"
template: >-
{% if color is defined %}
<span style="color: {{ color }};">
{% endif %}
<svg xmlns="http://www.w3.org/2000/svg"{{
attributes
.setAttribute('viewBox', attributes.viewBox|default('0 0 24 24'))
.setAttribute('width', size|default('32'))
.setAttribute('height', size|default('32'))
.setAttribute('fill', 'none')
.setAttribute('stroke-width', stroke|default('1.5'))
.setAttribute('aria-hidden', 'true')
.addClass('icon', class|default(''))
}}>
{{ content }}
</svg>
{% if color is defined %}
</span>
{% endif %}
Le setting size est aussi transmis : si le pack le déclare (c'est le cas des exemples de l'article précédent), l'icône reçoit son width/height de 20 pixels sans rien faire de plus.
Commentaires (0)