Construire une bibliothèque de composants avec React and Emotion
Selon Clearleft, une bibliothèque de composants est:
«Une collection de composants, organisée de manière significative, et souvent (mais pas nécessairement) un moyen de parcourir et de prévisualiser ces composants et leurs ressources associées. »
-« On Building Component Libraries »Clearleft
Nous allons apprendre à construire une bibliothèque de composants en en créant une qui comprend quatre composants:
Button
Un wrapper autour du bouton HTML par défautBox
Un conteneur (HTML div) avec des propriétés personnaliséesColumns
Un conteneur dont les enfants sont régulièrement espacés sur l'axe des xPile
Un conteneur dont les enfants sont régulièrement espacés sur l'axe des y
Ces composants pourraient alors être utilisés dans n'importe quelle application sur laquelle nous travaillons. Nous allons créer la bibliothèque de composants en utilisant React et Emotion.
À la fin de cet article, vous devriez être en mesure de créer une bibliothèque de composants qui correspond à tout cas d'utilisation que vous avez à l'esprit. Ces connaissances vous seront utiles lorsque vous travaillerez avec une équipe qui doit utiliser des composants réutilisables.
Commençons par définir ce qu'est la bibliothèque Emotion. La documentation explique:
«Emotion est une bibliothèque conçue pour écrire des styles CSS avec JavaScript. Il fournit une composition de style puissante et prévisible en plus d'une excellente expérience de développement avec des fonctionnalités telles que les cartes sources, les étiquettes et les utilitaires de test. »
-« Introduction »Emotion Docs
En substance, Emotion est une bibliothèque CSS-in-JavaScript, et une chose intéressante à propos des bibliothèques CSS-in-JavaScript est qu'elles vous permettent de colocaliser des composants avec des styles. Le fait de pouvoir les lier ensemble dans une portée garantit que certains styles de composants n'interfèrent pas avec d'autres, ce qui est crucial pour notre bibliothèque de composants.
Emotion expose deux API pour React:
@ emotion / core
@ emotion / styled
Avant de nous plonger dans le fonctionnement de ces API, notez qu'elles prennent toutes les deux en charge le style des composants avec des chaînes de modèle et des objets.
L'API de base est en fait similaire à l'API standard ] style
que nous utilisons actuellement lors de la création d'applications avec React, avec l'ajout d'un préfixe de fournisseur, de sélecteurs imbriqués, de requêtes multimédias, etc.
L'utilisation de l'approche objet avec l'API principale ressemblerait généralement à ceci: [19659027] importer {jsx} depuis '@ emotion / core'
laissez Box = accessoires => {
revenir (
)
}
Ceci est un exemple plutôt artificiel qui montre comment nous pourrions styliser un composant Box
avec Emotion. C'est comme remplacer la propriété style
par une propriété css
puis nous sommes prêts à partir.
Voyons maintenant comment nous pourrions utiliser l'approche de chaîne de modèle avec le même API principale:
import {jsx, css} depuis '@ emotion / core'
laissez Box = accessoires => {
revenir (
)
}
Tout ce que nous avons fait, c'est envelopper la chaîne de modèle avec la fonction de balise css
et Emotion s'occupe du reste.
L'API de style qui est construite sur l'API principale, adopte une approche légèrement différente pour styliser les composants. Cette API est appelée avec un élément HTML ou un composant React particulier, et cet élément est appelé avec un objet ou une chaîne de modèle qui contient les styles de cet élément.
Voyons comment nous pourrions utiliser l'approche objet avec l'API stylisée:
importation de style "@ emotion / styled"
const Box = styled.div ({
backgroundColor: 'gris'
});
Voici une façon d'utiliser l'API stylisée, qui est une alternative à l'utilisation de l'API principale. Les sorties rendues sont les mêmes.
Voyons maintenant comment nous pourrions utiliser l'approche de chaîne de modèle à l'aide de l'API stylisée:
import styled from '@ emotion / styled'
const Box = styled.div`
couleur de fond: gris
»
Ceci réalise la même chose que l'approche objet, seulement avec une chaîne de modèle cette fois.
Nous pourrions utiliser soit l'API principale, soit l'API stylisée lors de la construction de composants ou d'une application. Je préfère l'approche stylisée pour une bibliothèque de composants pour plusieurs raisons:
- Elle réalise beaucoup de choses en quelques frappes.
- Elle prend un accessoire
comme
ce qui aide à changer dynamiquement l'élément HTML depuis le site d'appel. Disons que nous utilisons par défaut un élément de paragraphe et que nous avons besoin d'un élément d'en-tête à cause de la sémantique; nous pouvons passer l'élément d'en-tête comme valeur à la propriétéas
.
Mise en route
Pour commencer, clonons les scripts de configuration sur GitHub, ce que nous pouvons faire sur la ligne de commande:
git clone git@github.com: smashingmagazine / component-library.git
Cette commande copie le code de ce référentiel dans le dossier de component-library
. Il contient le code requis pour configurer une bibliothèque de composants, qui inclut Rollup pour aider à regrouper notre bibliothèque.
Nous avons actuellement un dossier components
avec un index.js
qui ne fait rien. Nous allons créer de nouveaux dossiers dans le dossier components
pour chaque composant que nous construisons dans notre bibliothèque. Le dossier de chaque composant exposera les fichiers suivants:
Component.js
C'est le composant que nous construisons.-
index.js
Ceci exporte le composant depuisComponent.js
et facilite le référencement de composants à partir d'un emplacement différent. -
Component.story.js
Ceci rend essentiellement notre composant dans ses multiples états en utilisant Storybook.
Il est également livré avec un utils
qui définit certaines propriétés qui seraient utilisées dans nos composants. Le dossier contient plusieurs fichiers:
helpers.js
Il contient des fonctions d'assistance que nous allons utiliser dans toute notre application.-
units.js
Ceci définit les unités d'espacement et de taille de police , que nous utiliserons plus tard. -
theme.js
Ceci définit la palette, les ombres, la typographie et la forme de notre bibliothèque de composants.
Regardons ce que nous avons défini dans les unités .js
fichier:
export const spacing = {
aucun: 0,
xxsmall: '4px',
xsmall: '8px',
petit: '12px',
moyen: '20px',
gouttière: '24px',
grand: '32px',
xlarge: '48px',
xxlarge: '96px',
};
export const fontSizes = {
xsmall: '0.79rem',
petit: '0.889rem',
moyen: '1rem',
grand: '1.125rem',
xlarge: '1.266rem',
xxlarge: '1.424rem',
};
Ceci définit les règles d'espacement
et fontSizes
. La règle d'espacement a été inspirée par le Braid design system qui est basé sur des multiples de quatre. Les fontSizes
sont dérivées de l'échelle de type second majeur (1,125), qui est une bonne échelle pour les sites Web de produits. Si vous êtes curieux d'en savoir plus sur l'échelle de type, « Exploring Responsive Type Scales » explique l'intérêt de connaître les échelles appropriées pour différents sites Web.
Passons ensuite au theme.js
fichier!
import {espacement} de './units';
const white = '#fff';
const noir = '# 111';
palette const = {
commun: {
noir,
blanc,
},
primaire: {
principal: '# 0070F3',
lumière: '# 146DD6',
contrastText: blanc,
},
Erreur: {
principal: '# A51C30',
lumière: '# A7333F',
contrastText: blanc,
},
gris: {
100: «#EAEAEA»,
200: «# C9C5C5»,
300: «# 888»,
400: «# 666»,
},
};
const shadows = {
0: 'aucun',
1: '0px 5px 10px rgba (0, 0, 0, 0.12)',
2: '0px 8px 30px rgba (0, 0, 0, 0.24)',
};
typographie const = {
famille de polices:
"Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Ubuntu, 'Helvetica Neue', sans-serif",
};
forme const = {
borderRadius: espacement ['xxsmall'],
};
export const theme = {
palette,
ombres,
typographie,
forme,
};
Dans le fichier de thème, nous avons défini notre palette
qui correspond essentiellement aux couleurs que nous allons utiliser pour tous les composants de notre bibliothèque. Nous avons également un objet shadows
où nous définissons nos valeurs box-shadow
. Il y a aussi l’objet typography
qui ne définit actuellement que notre fontFamily
. Enfin, shape
est utilisé pour des propriétés telles que border-radius
. La structure de ce thème est inspirée de Material-UI .
Ensuite, notre fichier helpers.js
!
export const isObjectEmpty = (obj) => {
return Object.keys (obj) .length === 0;
};
Ici, nous exposons uniquement la fonction isObjectEmpty
qui prend un objet et renvoie true
si l'objet est vide. Il renvoie false
s'il a des valeurs. Nous allons utiliser cette fonction plus tard.
Maintenant que nous avons parcouru tous les fichiers du dossier utils
il est temps de commencer à construire nos composants!
Boutons
Les boutons sont l'un des composants les plus utilisés sur le Web. Ils sont utilisés partout et peuvent prendre différentes formes, formes, tailles et plus encore.
Voici les boutons que nous allons intégrer Figma .

Ces variations subtiles vont être appliquées comme propriétés à notre bouton. Nous aimerions que les boutons de notre bibliothèque de composants acceptent des propriétés telles que variant
size
enableElevation
(ie box-shadow
), et couleur
.
En commençant par le composant bouton, créons un dossier Button
dans lequel nous définirons tout ce qui concerne les boutons, comme indiqué précédemment.
Créons notre bouton composant:
import stylisé à partir de '@ emotion / styled';
import isPropValid de '@ emotion / is-prop-valid';
const StyledButton = () => {};
const IGNORED_PROPS = ['color'];
const buttonConfig = {
shouldForwardProp: (prop) =>
isPropValid (prop) &&! IGNORED_PROPS.includes (prop),
};
export const Button = styled ('button', buttonConfig) (StyledButton);
Ici, nous avons commencé par configurer notre composant de bouton avec un buttonConfig
. Le bouton buttonConfig
contient shouldForwardProp
qui est utilisé pour contrôler les propriétés à transmettre au DOM, car des propriétés telles que color
apparaissent sur l'élément rendu par default.
Ensuite, définissons la taille de nos boutons, que nous allons utiliser dans le composant button!
const buttonSizeProps = {
petit: {
fontSize: fontSizes ['xsmall'],
padding: `$ {spacing ['xsmall']} $ {spacing ['small']}`,
},
moyen: {
fontSize: fontSizes ['small'],
padding: `$ {spacing ['small']} $ {spacing ['medium']}`,
},
grand: {
fontSize: fontSizes ['medium'],
padding: `$ {spacing ['medium']} $ {spacing ['large']}`,
},
};
buttonSizeProps
est une carte de nos valeurs de taille ( small
medium
et large
), et renvoie fontSize
et padding
valeurs basées sur les tailles. Pour un petit bouton, nous aurions besoin d'une petite police avec un petit remplissage. Il en va de même pour les tailles moyennes et grandes pour les mettre à l'échelle de manière appropriée.
Ensuite, définissons une fonction qui fournit des propriétés CSS valides basées sur la variante passée:
const getPropsByVariant = ({variant, color, theme}) = > {
const colorInPalette = theme.palette [color];
variantes const = {
contour: colorInPalette
? contourVariantAccessoiresParPalette
: defaultOutlineVariantProps,
solide: colorInPalette
? solidVariantPropsByPalette
: defaultSolidVariantProps,
};
variantes de retour [variant] || variants.solid;
};
Ici, la fonction getPropsByVariant
prend les propriétés variant
color
et theme
et renvoie les propriétés de la variante spécifiée; si aucune variante n'est spécifiée, la valeur par défaut est solid
. colorInPalette
récupère la palette affectée à la couleur spécifiée si elle est trouvée, et undefined
si elle n'est pas trouvée dans notre objet theme
.
Dans chaque variante, nous vérifions si une palette existe réellement pour la couleur spécifiée; sinon, nous utilisons les couleurs des objets communs
et gray
de notre thème, que nous appliquerons dans defaultOutlineVariantProps
et defaultSolidVariantProps
.
Ensuite, définissons nos propriétés de variante!
const defaultSolidVariantProps = {
principale: {
border: `1px solid $ {theme.palette.grey [100]}`,
backgroundColor: theme.palette.grey [100],
couleur: theme.palette.common.black,
},
survol: {
border: `1px solid $ {theme.palette.grey [200]}`,
backgroundColor: theme.palette.grey [200],
},
};
const defaultOutlineVariantProps = {
principale: {
border: `1px solid $ {theme.palette.common.black}`,
backgroundColor: theme.palette.common.white,
couleur: theme.palette.common.black,
},
survol: {
border: `1px solid $ {theme.palette.common.black}`,
backgroundColor: theme.palette.common.white,
couleur: theme.palette.common.black,
},
};
const solidVariantPropsByPalette = colorInPalette && {
principale: {
bordure: `1px solide $ {colorInPalette.main}`,
backgroundColor: colorInPalette.main,
couleur: colorInPalette.contrastText,
},
survol: {
bordure: `1px solide $ {colorInPalette.light}`,
backgroundColor: colorInPalette.light,
},
};
const contourVariantPropsByPalette = colorInPalette && {
principale: {
bordure: `1px solide $ {colorInPalette.main}`,
backgroundColor: theme.palette.common.white,
couleur: colorInPalette.main,
},
survol: {
bordure: `1px solide $ {colorInPalette.light}`,
backgroundColor: theme.palette.common.white,
couleur: colorInPalette.light,
},
};
Ici, nous définissons les propriétés qui vont être appliquées à notre bouton en fonction des variantes sélectionnées. Et, comme indiqué précédemment, defaultSolidVariantProps
et defaultOutlineVariantProps
utilisent les couleurs de nos objets communs
et gray
comme solutions de secours lorsque la couleur spécifiée n'est pas t dans notre palette ou lorsqu'aucune couleur n'est spécifiée pour ce que nous avons mis en place.
En passant, les objets solidVariantPropsByPalette
et outlineVariantPropsByPalette
utilisent la couleur de notre palette comme spécifié par le bouton. Ils ont tous deux des propriétés main
et hover
qui différencient respectivement les styles par défaut et survol du bouton.
La conception du bouton que nous avons utilisée compte pour deux variantes, que nous pouvons vérifier dans la conception de notre bibliothèque de composants .
Ensuite, créons notre fonction StyledButton
qui combine tout ce que nous avons fait jusqu'à présent.
const StyledButton = ({
Couleur,
Taille,
une variante,
enableElevation,
désactivé,
thème,
}) => {
if (isObjectEmpty (thème)) {
thème = defaultTheme;
}
const fontSizeBySize = buttonSizeProps [size] ?. fontSize;
const paddingBySize = buttonSizeProps [size] ?. padding;
const propsByVariant = getPropsByVariant ({variante, thème, couleur});
revenir {
fontWeight: 500,
curseur: 'pointeur',
opacité: désactivée && 0.7,
transition: 'tout 0,3s linéaire',
remplissage: buttonSizeProps.medium.padding,
fontSize: buttonSizeProps.medium.fontSize,
borderRadius: theme.shape.borderRadius,
fontFamily: theme.typography.fontFamily,
boxShadow: enableElevation && theme.shadows [1],
... (propsByVariant && propsByVariant.main),
... (paddingBySize && {padding: paddingBySize}),
... (fontSizeBySize && {fontSize: fontSizeBySize}),
'&: hover':! désactivé && {
boxShadow: enableElevation && theme.shadows [2],
... (propsByVariant && propsByVariant.hover),
},
};
};
Dans la fonction StyledButton
nous affectons defaultTheme
au thème si l'objet theme
est vide, ce qui le rend facultatif pour les consommateurs de notre bibliothèque d'utiliser le ThemeProvider d'Emotion pour utiliser la bibliothèque. Nous avons attribué fontSize
et padding
en fonction de l'objet buttonSizeProps
. Nous avons défini plusieurs propriétés de bouton par défaut, telles que fontWeight
et cursor
qui ne sont liées à aucune propriété, et nous avons également dérivé color
backgroundColor
et border
valeurs basées sur le résultat de propsByVariant
.
Maintenant que nous avons créé notre composant Button
voyons comment nous pouvons utilisez-le:
Nous pouvons vérifier à quoi cela ressemble sur CodeSandbox :
Voilà comment utiliser le composant Button
. Nous définissons les propriétés suivantes:
- Nous définissons une variante avec une valeur
solide
. Nous aurions pu spécifiercontour
à la place. Si l'accessoirevariante
n'est pas fourni, nous utiliserons également par défautsolid
. - Nous définissons
couleur
avec une valeur deprimary
. Nous prenons également en charge l'erreurtheme
. Si la propriétécolor
n'est pas spécifiée, nous reviendrons à notre état de couleur par défaut. - Nous définissons
size
avec une valeur desmall
. Il peut s'agir demedium
(valeur par défaut) oularge
. - Nous définissons
EnableElevation
parce que nous voulons debox-shadow
sur notre bouton. Nous aurions pu choisir de ne pas l'utiliser. - Enfin, nous définissons
disabled
parce que nous voulons que notre bouton soit désactivé. La chose supplémentaire que nous faisons pour un bouton désactivé est de réduire son opacité.
Le bouton n'a pas besoin de prendre de propriété. Il est par défaut un bouton plein de taille moyenne.
Composant de boîte
Un composant de boîte est un conteneur qui peut contenir n'importe quel composant ou élément HTML. Il accepte mais n'est pas limité à des propriétés telles que padding
margin
display
et width
. Il peut également être utilisé comme composant de base pour certains des autres composants que nous aborderons plus tard.
Voici à quoi il ressemble sur Figma:

Avant de plonger dans le code, n'oublions pas de créer un nouveau dossier pour ce composant.
Maintenant, créons notre Box
composant:
import stylisé à partir de «@ emotion / styled»;
import isPropValid de '@ emotion / is-prop-valid';
import {espacement, thème par défautThème} de '../../utils';
const StyledBox = ({
paddingX,
paddingY,
marginX,
marginY,
largeur,
afficher,
thème,
... accessoires
}) => {
if (isObjectEmpty (thème)) {
thème = defaultTheme;
}
remplissage const = espacement [props.padding];
let paddingTop = espacement [props.paddingTop];
let paddingRight = espacement [props.paddingRight];
let paddingBottom = espacement [props.paddingBottom];
laissez paddingLeft = espacement [props.paddingLeft];
if (paddingX) {
paddingLeft = espacement [paddingX];
paddingRight = espacement [paddingX];
}
if (paddingY) {
paddingTop = espacement [paddingY];
paddingBottom = espacement [paddingY];
}
let margin = espacement [props.margin];
let marginTop = espacement [props.marginTop];
let marginRight = espacement [props.marginRight];
let marginBottom = espacement [props.marginBottom];
let marginLeft = espacement [props.marginLeft];
if (marginX) {
marginLeft = espacement [marginX];
marginRight = espacement [marginX];
}
if (marginY) {
marginTop = espacement [marginY];
marginBottom = espacement [marginY];
}
revenir {
rembourrage,
paddingTop,
paddingRight,
rembourrage en bas,
rembourrageGauche,
marge,
marginTop,
marginRight,
marginBottom,
marginLeft,
largeur,
afficher,
fontFamily: theme.typography.fontFamily,
};
};
const IGNORED_PROPS = ['display', 'width'];
const boxConfig = {
shouldForwardProp: (prop) =>
isPropValid (prop) &&! IGNORED_PROPS.includes (prop),
};
export const Box = styled ('div', boxConfig) (StyledBox);
La règle d'espacement
que nous avons définie précédemment est appliquée à la fois au remplissage et à la marge, comme nous pouvons le voir dans le composant Box
. Nous recevons des valeurs contextuelles pour le remplissage et la marge, et nous recherchons leurs valeurs réelles à partir de l'objet spacing
.
Nous acceptons les accessoires paddingX
et paddingY
à mettre à jour remplissage sur l'axe horizontal et vertical, respectivement. Nous faisons de même pour marginX
et marginY
.
De plus, nous ne voulons pas des accessoires display
et width
pour être transmis au DOM car nous n'en avons besoin qu'en CSS. Nous les ajoutons donc à notre liste d'accessoires à ignorer, et les transmettons à notre configuration.
Voici comment utiliser le composant Box
:
Composant de boîte simple
Nous pouvons voir à quoi cela ressemble sur CodeSandbox .
Dans ce composant Box
nous avons attribué small
comme valeur à notre padding
et medium
aux propriétés paddingTop
et paddingBottom
. Une fois rendu, le composant Box
aura ses propriétés padding-left
et padding-right
définies sur 12px
chacune, et ses ] propriétés padding-top
et padding-bottom
définies sur 20px
. Nous aurions pu remplacer paddingTop
et paddingBottom
par paddingY
et obtenir le même résultat.
Columns Component
The Columns
component est une variante de notre composant Box
avec un affichage
de flex
et avec des enfants régulièrement espacés sur l'axe des x.
Voici une représentation du composant Columns
dans Figma:

Construisons notre Colonnes
composant!
import React de 'react';
import {Box} de '../Box';
export const Columns = ({children, space, ... props}) => {
revenir (
{React.Children.map (enfants, (enfant, index) => {
if (child.type! == Box) {
console.warn (
"Chaque enfant d'un composant Columns doit être un composant Box"
);
}
if (index> 0) {
return React.cloneElement (enfant, {
marginLeft: espace,
largeur: '100%',
});
}
return React.cloneElement (enfant, {largeur: '100%'});
})}
);
};
Nous utilisons React.Children pour cartographier les enfants du composant Columns
. Et nous ajoutons les propriétés marginLeft
et width
à chacun des enfants, à l'exception du premier enfant, qui n'a pas besoin d'une propriété marginLeft
car c'est le plus à gauche enfant dans la colonne. Nous nous attendons à ce que chaque enfant soit un élément Box
pour s’assurer que les styles nécessaires lui sont appliqués.
Voici comment utiliser le composant Columns
:
Rubrique 1
Rubrique 2
Rubrique 3
Nous pouvons voir à quoi cela ressemble sur CodeSandbox .
Les enfants Columns
sont espacés uniformément sur l'axe des x de 12 pixels parce que c'est la valeur de small
se résout à, comme nous l'avons défini précédemment. Comme le composant Columns
est littéralement un composant Box
il peut intégrer d'autres propriétés du composant Box
et nous pouvons le personnaliser autant que nous le voulons. [19659134] Composant Stack
Il s'agit également d'une variante de notre composant Box
qui prend toute la largeur de l'élément parent et dont les enfants sont régulièrement espacés sur l'axe y.
Voici une représentation de le composant Stack
dans Figma:

Construisons notre Composant de pile
:
import React de 'react';
importer {Box} depuis '../Box';
import {Columns} de '../Columns';
const StackChildrenTypes = [Box, Columns];
const UnsupportedChildTypeWarning =
'Chaque enfant d'un composant Stack doit être de l'un des types: Boîte, Colonnes';
export const Stack = ({enfants, espace, ... accessoires}) => {
revenir (
{React.Children.map (enfants, (enfant, index) => {
if (! StackChildrenTypes.includes (child.type)) {
console.warn (UnsupportedChildTypeWarning);
}
if (index> 0) {
return React.cloneElement (enfant, {marginTop: espace});
}
retour de l'enfant;
})}
);
};
Ici, nous mappons chaque enfant avec React.Children
et nous lui appliquons une propriété paddingTop
avec la valeur de l'argument space
. En ce qui concerne le premier enfant, nous avons besoin qu'il prenne sa position d'origine, donc nous sautons l'ajout d'une propriété marginTop
. Nous acceptons également que chaque enfant soit une Box
afin que nous puissions lui appliquer les propriétés nécessaires.
Voici comment utiliser le composant Stack
:
Rubrique 1
Rubrique 2
Rubrique 3
Nous pouvons voir à quoi cela ressemble sur CodeSandbox .
Ici, les éléments Box
sont espacés uniformément avec la petite unité
et la première La case
prend une propriété marginTop
distincte. Cela montre que vous pouvez personnaliser les composants comme vous le souhaitez.
Conclusion
Nous avons passé en revue les bases de l'utilisation d'Emotion pour créer des composants dans React en utilisant les API qu'il fournit. Il ne s'agit que de l'une des nombreuses façons de créer une bibliothèque de composants. Il y a quelques nuances à le construire pour une marque, car vous n'aurez peut-être pas à prendre en compte le thème et d'autres éléments. Mais si vous prévoyez de rendre la bibliothèque publique un jour, vous devrez alors traiter les demandes pour ces pièces manquantes, alors considérez cette possibilité et rendez la bibliothèque un peu flexible à l'avance.
Si vous en avez questions, n'hésitez pas à les déposer en tant que commentaires.
Le référentiel de cet article est sur GitHub, et les designs de boutons que nous avons utilisés sont sur Figma.
Références

Source link