Icônes
Le petit jeu d'icônes du core, les icônes propres à un module ou à un projet, la navigation par nom et le sélecteur d'icône d'un bloc.
Le core ne livre que les icônes de sa propre interface, une soixantaine. Un module recopie celles qui lui manquent dans son propre code, et un projet apporte les siennes, d'où qu'elles viennent. Une icône dont personne n'a besoin n'est donc jamais installée.
Règle : ne jamais dessiner une icône à la main dans un composant. Une icône vient d'un fichier d'icône, du core, du module ou du projet.
Import
import { Add01Icon, Delete02Icon } from '@fraza/core/shared/icons';
import { Sun01Icon } from '@fraza/core/shared/icons/Sun01Icon';
<Add01Icon width={18} height={18} />Chaque icône accepte les props d'un <svg>. Elle hérite de currentColor, donc la couleur se pilote par la classe du parent.
Les deux formes sont client-safe. Chaque icône vit dans son propre fichier, lib/<Nom>.js, et le sous-chemin @fraza/core/shared/icons/<Nom> ne charge que ce fichier.
Un fichier qu'une page publique atteint importe ses icônes par leur sous-chemin. Le barrel ne charge lui aussi que les icônes importées en build de production, parce que le core déclare ses icônes sans effet de bord. Voir Effets de bord et barrels. Le sous-chemin reste la forme sûre : il tient aussi en développement et ne dépend pas du bundler. Le core fait de même dans tous les fichiers qu'une page publique atteint : core/themes et core/toast, tirés par FrazaProvider, et les pages d'authentification.
Le jeu du core
| Usage | Icônes |
|---|---|
| Actions | Add01Icon, Delete02Icon, PencilEdit01Icon, Copy01Icon, ClipboardCopyIcon, Download02Icon, RepeatIcon, Search01Icon, Settings02Icon, Logout02Icon |
| États | Tick02Icon, Cancel01Icon, CancelCircleIcon, Alert01Icon, AlertCircleIcon, InformationCircleIcon, InboxIcon |
| Navigation | ArrowDown01Icon, ArrowLeft01Icon, ArrowRight01Icon, Menu01Icon, MoreHorizontalIcon, DragDropVerticalIcon, ExternalLinkIcon, Link01Icon, Link02Icon |
| Admin | DashboardSquare03Icon, UserMultiple02Icon, User03Icon, Crown03Icon, Wrench01Icon, Layers01Icon, Image01Icon, Book02Icon, BookBookmark02Icon, SmartPhone01Icon |
| Thème | Sun01Icon, Moon02Icon, ComputerIcon |
| Éditeur | TextIcon, Heading01Icon à Heading06Icon, LeftToRightListBulletIcon, LeftToRightListNumberIcon, LeftToRightBlockQuoteIcon, CheckListIcon, CodeSimpleIcon, SourceCodeIcon, SeparatorHorizontalIcon, SpacingVerticalIcon, PlaySquareIcon, HighlighterIcon, TextColorIcon, TextClearIcon, AlignLeftIcon, AlignCenterIcon, AlignRightIcon, AlignFullWidthIcon |
Ce jeu fait partie de l'API publique du core. Un module importe d'ici les icônes qui y figurent plutôt que de les recopier. En retour, retirer une icône du jeu est un changement cassant : tout module qui l'importe casse au build.
Le jeu ne grandit que pour l'interface du core. Une icône dont seul un module a besoin va dans ce module.
Provenance et licence
Les icônes viennent du jeu libre Hugeicons, style « stroke rounded » : un trait arrondi, sans remplissage. Elles sont recopiées depuis le paquet @hugeicons/core-free-icons (version 4.3.5), sous licence MIT, Copyright (c) 2025 Hugeicons. Les tracés sont repris tels quels. Seule l'épaisseur du trait change : 1,8 au lieu de 1,5, la valeur de la charte. SpacingVerticalIcon n'a pas d'équivalent dans le jeu libre : il garde son nom, mais dessine ParagraphSpacing, l'icône libre la plus proche.
Jamais d'icône Hugeicons Pro dans un paquet publié. Les styles Pro (solid, duotone, twotone, bulk…) et les icônes absentes du jeu libre sont sous une licence commerciale qui interdit de les redistribuer. Le core et les modules sont publiés : une icône Pro recopiée dans src/ serait distribuée à chaque installation. Une icône qui manque se cherche dans le jeu libre, ou se remplace par la plus proche qui y figure. On ne prend pas non plus une variante pleine pour un état actif : l'état se marque par la couleur.
Recopier ces icônes, c'est distribuer leur code avec le paquet. La licence MIT doit donc les accompagner. Elle figure dans THIRD-PARTY-NOTICES.md, sous « Code tiers intégré », produit par npm run build:notices à partir de la liste VENDORED de scripts/generate-notices.js. Changer de version ou de source des icônes, c'est mettre à jour cette entrée et l'en-tête de src/shared/icons/index.js.
Dans l'interface, une icône fait 16 px dans la navigation et les boutons (14 px dans un bouton sm). Elle prend la couleur du texte (currentColor).
Icônes d'un module
Un module recopie les icônes qui ne sont pas dans le jeu du core, une par fichier, dans src/icons/<Nom>.js :
// src/icons/InvoiceIcon.js
// Hugeicons, jeu libre « stroke rounded », licence MIT. Voir THIRD-PARTY-NOTICES.md.
export const InvoiceIcon = (props) => (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width={24} height={24} color={"currentColor"} fill={"none"} {...props}>
{/* tracés du jeu libre, strokeWidth="1.8" */}
</svg>
);import { InvoiceIcon } from '../icons/InvoiceIcon.js';
import { Add01Icon } from '@fraza/core/shared/icons';Un fichier par icône, parce qu'un module ne déclare pas "sideEffects": false : un fichier qui regrouperait toutes ses icônes partirait en entier dans chaque page qui en affiche une, y compris ses pages publiques.
Le module déclare l'entrée Hugeicons dans la liste VENDORED de son scripts/generate-notices.js, avec l'emplacement dist/icons/, pour que la licence MIT accompagne son paquet.
Deux modules peuvent recopier la même icône. Elle existe alors en double quand les deux sont installés : moins d'un kilo-octet, en échange de modules qui ne dépendent pas l'un de l'autre.
API
| Signature | Retour | Description |
|---|---|---|
registerAdminIcons(icons) | void | Enregistre des icônes par nom pour la navigation de l'admin. |
getAdminIcon(name) | composant ou null | Résout un nom : jeu du core, puis icônes enregistrées. |
Les deux s'importent depuis @fraza/core/features/admin, sûr côté client.
registerAdminIcons
La navigation de l'admin est construite côté serveur et passée au client en données sérialisables. Un composant ne traverse pas cette frontière : registerNavSection et registerNavItem reçoivent donc le nom d'une icône. Un nom du jeu du core se résout tout seul. Pour une icône du module ou du projet, enregistrer le composant sous ce nom dans le point d'entrée client, au même endroit que registerPage :
// src/client.js du module
'use client';
import { registerAdminIcons, registerPage } from '@fraza/core/features/admin';
import { InvoiceIcon } from './icons/InvoiceIcon.js';
registerAdminIcons({ InvoiceIcon });// src/register-server.js du module
registerNavSection({ id: 'billing', title: 'Facturation', icon: 'InvoiceIcon', order: 150, group: 'tools' });Le point d'entrée client s'exécute aussi pendant le rendu serveur des composants client. Un seul enregistrement couvre donc le rendu serveur et l'hydratation.
Le premier enregistré gagne : un nom désigne un dessin, et deux modules qui recopient la même icône la déclarent sous le même nom. Un nom du jeu du core n'est jamais remplacé. Une valeur qui n'est pas un composant React lève une erreur au démarrage.
getAdminIcon
Pour un composant d'admin qui reçoit un nom d'icône, par exemple l'icône d'un type de contenu déclarée dans l'environnement :
import { getAdminIcon } from '@fraza/core/features/admin';
import { Book02Icon } from '@fraza/core/shared/icons';
const Icon = getAdminIcon(name) || Book02Icon;Un nom inconnu renvoie null. La barre latérale affiche alors DashboardSquare03Icon.
Sélecteur d'icône d'un bloc
Pour laisser l'utilisateur choisir une icône, un bloc passe sa propre liste à IconField de @fraza/core/blocks-registry/fields-ui, ou à IconPicker de @fraza/core/shared/components. Le sélecteur renvoie le nom de l'icône, enregistré dans le contenu du bloc. Le rendu résout ce nom avec la même liste.
Le projet regroupe ses icônes dans un fichier partagé par ses blocs, components/fraza/icons.js, en exports nommés :
// components/fraza/blocks/icon-list/template.js
'use client';
import { IconField } from '@fraza/core/blocks-registry/fields-ui';
import * as ICONS from '../../icons.js';
<IconField label="Icône" icons={ICONS} value={item.icon} onChange={(icon) => patchItem(i, { icon })} />// components/fraza/blocks/icon-list/render.js
import * as ICONS from '../../icons.js';
const Icon = ICONS[item.icon] || null;La page publique ne charge ainsi que les icônes de cette liste, et rien d'autre. Il n'y a pas de catalogue global : une icône qui n'est pas dans la liste n'est ni proposée, ni affichée.
Le sélecteur cherche dans le nom et dans les mots-clés de chaque icône (Icon.keywords). Il ignore les exports qui ne sont pas des composants, donc un objet d'espace de noms (import * as) convient tel quel.
Icônes d'un paquet officiel
Un projet n'est pas redistribué : il peut puiser dans n'importe quel jeu, dans le respect de sa licence. Toute icône est un composant React qui accepte les props d'un <svg>, et une ligne suffit pour l'envelopper :
// components/fraza/icons.js
import { HugeiconsIcon } from '@hugeicons/react';
import { Home01Icon as Home01 } from '@hugeicons/core-free-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faGithub } from '@fortawesome/free-brands-svg-icons';
export const Home01Icon = (props) => <HugeiconsIcon icon={Home01} strokeWidth={1.8} {...props} />;
export const GithubIcon = (props) => <FontAwesomeIcon icon={faGithub} {...props} />;Ces icônes servent ensuite dans les listes des blocs, ou dans registerAdminIcons. Un strokeWidth de 1,8 les aligne sur la charte. Les icônes gratuites de Font Awesome demandent une attribution (CC BY 4.0). Un module publié, lui, ne s'appuie que sur des icônes libres recopiées.
Ajouter une icône au core
Seulement quand l'interface du core en a besoin.
- Prendre l'icône dans le jeu libre Hugeicons, style « stroke rounded » (paquet
@hugeicons/core-free-icons), recopier son tracé et passer chaquestrokeWidthà"1.8". - Créer
src/shared/icons/lib/<Nom>.js, du nom de l'export, sur le modèle des fichiers voisins : la ligne de provenance, le composant, puis ses mots-clés. - L'ajouter au barrel :
export { Nom } from './lib/Nom.js';dansindex.js. - L'ajouter au tableau du jeu, plus haut sur cette page.
// Hugeicons, jeu libre « stroke rounded », licence MIT. Voir THIRD-PARTY-NOTICES.md.
export const Add01Icon = (props) => (
<svg /* … */>{/* … */}</svg>
);
Add01Icon.keywords = ['add', 'plus', 'create', 'new', 'insert', 'ajouter', 'nouveau', 'créer', 'insérer'];Le sous-chemin @fraza/core/shared/icons/<Nom> existe dès que le fichier existe : package.json#exports le déclare par motif, il n'y a rien à y ajouter.
Convention des mots-clés
- Toujours en anglais et en français. C'est ce qui rend le sélecteur utilisable quelle que soit la langue dans laquelle l'utilisateur pense.
- Mots au sens large : synonymes et usages contextuels.
- Apostrophes : les mots-clés sont des chaînes délimitées par
'. Un mot commesac d'argentcasse la chaîne. Préférerargent, ou échapper :'sac d\'argent'.
Les mots-clés sont facultatifs. Une icône sans mots-clés se trouve par son nom.
Catalogue
Le devkit expose le jeu du core sous /admin/devkit/icons, activable par FRAZA_DEVKIT=true. Un clic copie le nom, Maj et clic copie le JSX. Voir Administration.
Notes
- Un nom enregistré dans un contenu dépend de la liste du bloc. Retirer une icône de
components/fraza/icons.jsla fait disparaître des blocs qui l'utilisaient, sans erreur. La renommer aussi. - Une icône de navigation inconnue ne lève pas d'erreur. Un nom mal orthographié, ou une icône enregistrée hors du point d'entrée client, s'affiche comme
DashboardSquare03Icon.