Depuis Drupal 11.1, le core embarque une API Icon : on déclare un pack d'icônes dans un fichier YAML, et une fonction Twig icon() les rend n'importe où (templates, SDC, render arrays). Côté back-office, le module contrib UI Icons ajoute ce que le core n'a pas : un type de champ, un sélecteur visuel et une page qui liste tous les packs installés.
Il est possible d'utiliser les kits d'icones classiques avec UI Icons, mais aussi se faire son kit personnalisé à partir de fichiers SVG. C'est ce que l'on va voir ici avec un kit personnalisé et l'intégration de la librairie opensource Iconoir.
Les modules
Nous allons commencer par installer UI Icons et activer les sous-modules
composer require drupal/ui_icons
drush en ui_icons ui_icons_field ui_icons_picker ui_icons_libraryui_icons_field: un type de champ « icône », qui stocke la référence sous la formepack_id:icon_id;ui_icons_picker: le sélecteur visuel dans les formulaires (une modale avec recherche, bien plus agréable qu'un select) ;ui_icons_library: une page d'inventaire sous/admin/appearance/ui/icons, pratique pour vérifier qu'un pack est bien découvert et montrer le catalogue au client.
Déclarer le pack dans le thème
Un pack se déclare dans un fichier <extension>.icons.yml, à la racine d'un module ou d'un thème.
Dans web/themes/custom/mon_theme/mon_theme.icons.yml :
mon_pack:
enabled: true
label: 'Mes icônes'
description: 'Icônes SVG du projet.'
version: '1.0.0'
license:
name: 'Proprietary'
url: 'https://exemple.fr'
gpl-compatible: false
extractor: svg
config:
sources:
- icons/*.svgLes SVG sont simplement déposés dans web/themes/custom/mon_theme/icons/, le nom du fichier devient l'identifiant de l'icône : toque.svg donne mon_pack:toque.
Deux choses à savoir :
- l'identifiant du pack est la clé YAML (
mon_pack), pas le nom de l'extension qui le porte. On peut donc déplacer la déclaration d'un module vers un thème (ou l'inverse) sans casser une seule référencepack_id:icon_idstockée en base. - les packs sont mis en cache de façon agressive : après tout ajout d'icône ou modification du YAML,
drush cr. Mais bon, si vous faite du drupal depuis quelques temps, vous avez l'habitude.
Normaliser les sources SVG
Les SVG livré dans le cadre de notre projet n'était pas tip top... La règle : aucune couleur ni épaisseur de trait en dur dans les sources. La peinture est portée par des attributs currentColor sur les tracés :
- icônes pleines :
fill="currentColor"; - icônes filaires :
fill="none" stroke="currentColor", sansstroke-width(il sera posé par le template).
<!-- Icône filaire normalisée -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path d="M13.145 7.907c..." fill="none" stroke="currentColor"
stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<!-- Icône pleine normalisée -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path d="M20.863 7.782c..." fill="currentColor"/>
</svg>Grâce à ça, l'icône hérite de la couleur du texte ambiant par défaut, et les réglages color et stroke du pack touchent toutes les icônes sans exception. Un export Illustrator ou Figma brut ne respecte jamais ces règles : il faut repasser sur chaque fichier livré, une fois, à l'entrée dans le pack.
On conserve en revanche le viewBox d'origine de chaque fichier, y compris pour les icônes non carrées (notre vague fait du 53,761 × 41,711). Le template s'en servira.
Les réglages : size, color, stroke
Un pack peut déclarer des settings, décrits en JSON Schema. UI Icons en fait un mini-formulaire dans le picker et le type de champ, et les valeurs arrivent dans le template comme variables Twig :
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. Défaut : couleur du texte."
type: 'string'
format: 'color'
stroke:
title: 'Stroke width'
description: 'Épaisseur du trait des icônes filaires.'
type: 'number'
default: 2
minimum: 1
maximum: 3
multipleOf: 0.1Un choix délibéré ici : size n'a pas de valeur par défaut. Nos composants dimensionnent leurs icônes en CSS (em, variables, media queries), un width/height en dur n'apporterait que des prises de tête.
Le template
Chaque pack fournit son template de rendu, directement dans le YAML. C'est lui qui reçoit content (le contenu extrait du SVG), attributes, icon_id et les settings :
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)
}}{% if size %} width="{{ size }}" height="{{ size }}"{% endif %}>{{ content }}</svg>
{% if color %}</span>{% endif %}Ligne par ligne, les points qui comptent :
- le viewBox vient du fichier source : l'extracteur
svgexpose les attributs de la racine du SVG dansattributes, on reprend doncattributes.viewBoxavec un repli sur0 0 24 24. C'est ce qui permet aux icônes non carrées de garder leurs proportions sans réglage supplémentaire ; aria-hidden="true"etfocusable="false": nos icônes sont décoratives, leur nom accessible vient toujours du texte ou de l'aria-labelqui les accompagne.focusable="false"les sort de l'ordre de tabulation sur les moteurs qui y mettent encore les SVG ;stroke-widthposé sur la racine : comme les sources n'en portent pas, une seule valeur pilote toutes les icônes filaires, et n'a aucun effet sur les pleines ;- la classe
icon--<id>dans le cas où l'on veut ajouter du style CSS au cas par cas.
À l'usage
Partout en Twig, via la fonction du core :
{{ icon('mon_pack', 'poele') }}
{{ icon('mon_pack', 'arrow', { size: 32, color: '#2e5941' }) }}En PHP, via un render array :
$build['icon'] = [
'#type' => 'icon',
'#pack_id' => 'mon_pack',
'#icon_id' => 'poele',
'#settings' => ['size' => 32],
];Et côté contenu, le champ de ui_icons_field stocke la valeur sous forme pack_id:icon_id. Dans un composant SDC qui reçoit cette valeur en via une propriété on peut splitter :
{% set icon_parts = icon|split(':') %}
{{ icon(icon_parts[0], icon_parts[1]) }}Le contributeur choisit son icône dans le picker, avec aperçu et recherche, et peut piocher indifféremment dans le pack maison ou dans Iconoir : les packs se cumulent.
Installation et utilisation d'une librairie tierce : Iconoir
Le même mécanisme sert à exposer une bibliothèque installée par npm, sans module dédié. Les sources acceptent des motifs avec les jokers {group} et {icon_id} :
iconoir:
enabled: true
label: '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}.svgLe chemin absolu part du docroot (dans le projet courant nous les paquets npm front sont installés dans web/libraries/ non versionné).
Pour mon infra uzinasit j'ai créé des sous-modules pour iconoir, font-awesome... histoire de pouvoir activer un ou plusieurs pack directement, par site. Je me note de mettre le code ici à l'occasion.
Commentaires (0)