Variants de composants
Un variant est un jeu de props nommé, que l'on applique à un composant SmartCommon pour en modifier l'apparence sans le surcharger à chaque usage. C'est le mécanisme qui permet d'avoir un "bouton arrondi" ou une "liste compacte" dans toute l'application sans répéter les classes Tailwind.
Anatomie d'un variant
Un variant est un objet dont les clés décrivent les éléments internes du composant.
const rounded = {
buttonProps: {
className: "p-app-base rounded-full",
},
};
Deux formes de clés, reconnues à leur écriture :
| Forme | Signification | Exemple |
|---|---|---|
<nom>Props |
props d'un élément HTML interne | buttonProps, labelProps, iconProps |
<Nom> avec majuscule |
props d'un sous-composant SmartCommon | Spinner, Tag |
Les clés en majuscule sont fusionnées récursivement : un variant de Button peut donc piloter le Spinner qu'il affiche pendant un chargement.
const outlined = {
buttonProps: {
className: "text-gray-800 bg-white border",
},
Spinner: {
spinnerProps: {
className: "border-primary border-l-secondary",
},
},
};
Pour connaître les clés disponibles sur un composant, regardez les appels à mergeProps("...") dans sa source : chacun correspond à une clé de variant. Pour Button : button, label, icon, badge et le sous-composant Spinner.
Appliquer un variant
La prop variant accepte trois formes, combinables :
// 1. Par son nom (variant natif ou declare dans la configuration)
<Button variant="rounded" />
// 2. En objet, directement
<Button variant={{ buttonProps: { className: "rounded-full" } }} />
// 3. En tableau, applique de gauche a droite
<Button variant={["rounded", "uppercase", { labelProps: { className: "text-xs" } }]} />
Ordre et règles de fusion
Les sources sont appliquées dans cet ordre, la dernière gagne :
- le variant du thème actif, s'il en désigne un pour ce composant
- les entrées de la prop
variant, dans l'ordre du tableau - les props passées directement au composant
La fusion suit trois règles selon la propriété :
| Propriété | Règle |
|---|---|
className |
fusion par twMerge : les classes Tailwind conflictuelles sont résolues, la dernière l'emporte |
style |
fusion par étalement, clé par clé |
| toute autre prop | écrasement pur, sauf si la nouvelle valeur est undefined |
Astuce
Le passage par twMerge est la raison pour laquelle un variant peut "annuler" une classe du composant de base : rounded-full remplace proprement rounded-md, au lieu de se retrouver en concurrence dans l'attribut class.
Une valeur de className ou de style peut aussi être une fonction. Elle reçoit les paramètres publiés par le composant via setParams, ce qui permet un style dépendant de l'état interne.
Variants natifs
SmartCommon livre aujourd'hui cinq variants natifs, tous sur Button :
| Variant | Effet |
|---|---|
rounded |
bouton entièrement arrondi, avec un padding adapté |
outlined |
fond blanc, texte sombre, bordure ; ajuste aussi le Spinner |
uppercase |
libellé en capitales, graisse normale, interlettrage élargi |
reverse |
inverse l'ordre de l'icône et du libellé |
floatingRight |
positionne le bouton en flottant, en bas à droite |
<Button variant="outlined">Annuler</Button>
<Button variant={["rounded", "uppercase"]}>Valider</Button>
Note
Les autres composants exposent un dossier variants/ dans leur source, mais ces fichiers sont encore des emplacements réservés, sans contenu. Pour tout composant autre que Button, passez par un variant personnalisé.
Variants personnalisés
Les variants propres à votre application se déclarent dans la configuration passée au Provider, sous components.variants, indexés par nom de composant puis par nom de variant.
// src/appConfig.js
export const appConfig = {
components: {
variants: {
Button: {
danger: {
buttonProps: {
className: "bg-red-600 text-white hover:bg-red-700",
},
},
ghost: {
buttonProps: {
className: "bg-transparent border-none shadow-none",
},
},
},
ListItem: {
compact: {
itemProps: { className: "py-1 text-sm" },
},
},
},
},
};
// src/main.jsx
<Provider config={appConfig}>
<Router />
</Provider>
Ils s'utilisent ensuite comme les variants natifs :
<Button variant="danger">Supprimer</Button>
<ListItem variant="compact" />
Si un nom personnalisé reprend un nom natif, les deux définitions fusionnent.
Important
Ce mécanisme a longtemps été inopérant, et le point mérite d'être connu si vous reprenez un projet ancien : jusqu'à un correctif récent de SmartCommon, aucune forme de variant ne s'appliquait, à la seule exception d'un nom natif passé dans un tableau (variant={["rounded"]}). Ni les noms en chaîne, ni les objets, ni les thèmes, ni la configuration du Provider. Si vous constatez qu'un variant est sans effet, vérifiez d'abord la version de SmartCommon avant de chercher dans votre code.
Thèmes
Un thème associe, pour chaque composant, un ou plusieurs variants à appliquer par défaut dans toute l'application.
components: {
theme: "compact",
themes: {
compact: {
Button: "rounded",
ListItem: ["compact", "borderless"],
},
},
}
Le variant du thème est appliqué avant la prop variant, qui peut donc le surcharger ponctuellement.
Note
Ne pas confondre components.theme, qui désigne un jeu de variants, avec la prop theme du Provider, qui gère le mode clair, sombre ou automatique. Les deux sont indépendants. Voir Thèmes.
Composants qui acceptent des variants
Plus de 70 composants passent par useVariantMerger et acceptent donc une prop variant.
| Famille | Composants |
|---|---|
| Formulaire | Input, Select, SearchableSelect, Textarea, Checker, Boolean, RadioBar, Range, Rater, ColorPicker, Editor, Calendar, PlainCalendar, NumericPad, PinPad, Timer, Gps, AddressInput, Array, SignaturePad, PhotosUploader, VideosUploader, AudiosUploader |
| Affichage | Address, Array, Color, Coordinates, Datetime, Duration, Email, Files, Icon, Number, PhoneNumber, Signature, String, Tags, Text, Url |
| Éléments | Button, FAB, Spinner, Tag |
| Mise en page | Page, Block, Panel, Popup, List, ListItem |
| Navigation | Navbar, Sidebar, Tabbar, TabbarItem, ChipBar, LowerNavbarItem, UpperNavbarItem |
Accepter des variants dans son propre composant
Un composant applicatif peut utiliser le même mécanisme, avec useVariantMerger.
import { useVariantMerger } from '@cap-rel/smartcommon';
export const MyCard = (props) => {
const { variantProps, mergeProps, setParams } = useVariantMerger("MyCard", props);
const { title, children } = variantProps;
return (
<div {...mergeProps("card", props => ({
...props,
className: "rounded-lg border p-4",
}))}>
<h3 {...mergeProps("title", props => ({
...props,
className: "font-semibold",
}))}>
{title}
</h3>
{children}
</div>
);
};
Ce qu'il faut retenir :
- le premier argument de
useVariantMergerest la clé du composant, celle qu'on utilisera dans la configuration variantPropscontient les props après fusion : lisez-y vos props métier, paspropsdirectement- chaque
mergeProps("<clé>", ...)crée un point d'accroche pour les variants - une clé en majuscule (
mergeProps("Spinner", ...)) désigne un sous-composant et se fusionne récursivement
Pièges à connaître
| Symptôme | Cause |
|---|---|
| un variant nommé n'a aucun effet | version de SmartCommon antérieure au correctif de résolution des variants |
| un variant fonctionne en tableau mais pas en chaîne | même cause : c'est la signature exacte du défaut corrigé |
| une classe Tailwind est ignorée | conflit résolu par twMerge en faveur d'une source plus prioritaire ; vérifiez l'ordre de fusion |
une prop métier est undefined dans le composant |
elle est lue sur props au lieu de variantProps |
| le variant s'applique au mauvais élément | mauvaise clé : buttonProps cible l'élément, Button cible un sous-composant |
| une classe personnalisée ne fusionne pas | twMerge ne la connaît pas ; la déclarer dans components.tailwindCss.mergedClass |
Voir aussi
- SmartCommon - liste des composants
- Thèmes - mode clair et sombre, variables CSS
- Configuration du Provider
- Composants et pages