Premiers pas avec Storybook dans React



Avez-vous déjà songé à regrouper l’ensemble de vos éléments d’interface utilisateur dans un seul endroit au sein de React ?

Si vous débutez avec React, cette pratique ne vous est probablement pas encore familière.

De quoi parle-t-on exactement ?

Observez les exemples fournis par react-beautiful-dnd.

Ce que vous y voyez se nomme des « stories », ou « récits » en français. Et l’outil utilisé pour les générer est Storybook.

Maintenant, vous comprenez le sujet de cet article. Sans plus attendre, explorons ensemble.

Qu’est-ce que Storybook ?

Storybook est un environnement de développement d’interface utilisateur isolé, une sorte de terrain de jeu dédié à vos composants. Il permet d’expérimenter avec vos composants sous diverses configurations, sans avoir besoin de lancer l’application principale. Storybook fonctionne grâce à un serveur indépendant.

Storybook n’est pas exclusif à React. Il est compatible avec la plupart des frameworks frontaux, comme Vue, Angular, Mithril, Marko, Svelte et d’autres.

Pour plus d’informations sur Storybook, consultez cette page.

Qu’est-ce qu’une « story » ?

Une « story » définit l’état affiché d’un composant. Prenons un composant commun, comme un bouton par exemple. Il peut être utilisé de multiples façons, avec différents paramètres. Pour chaque état possible, nous pouvons créer une « story ».

Imaginons un composant Button.

Un bouton peut avoir divers états, tels que désactivé, en cours de chargement, principal, secondaire, petit, grand, moyen, etc. Si l’on devait énumérer tous les états, le tutoriel deviendrait fastidieux. Vous l’aurez compris. La pratique avec Storybook vous éclairera davantage.

Vous pouvez voir des exemples de « stories » pour un bouton dans différents cas (Large, Medium, Small).

Configuration de Storybook dans un projet

Nous allons maintenant installer Storybook dans un projet React.

C’est parti.

  • Créez un nouveau projet React avec la commande suivante, en lui donnant le nom de votre choix.
npx create-react-app storybook-demo
  • Puis, installez Storybook dans votre projet grâce à cette commande :
npx sb init

L’installation de Storybook est à présent terminée.

Storybook utilise son propre serveur.

Comment le démarrer ?

Storybook ajoute automatiquement une commande à votre fichier de script. Vous pouvez le vérifier dans la section « scripts » du fichier « package.json ». Pour lancer le serveur Storybook, exécutez la commande suivante :

npm run storybook

Storybook lancera un nouveau serveur sur le port spécifié dans la configuration de « package.json ». Il ouvrira automatiquement Storybook dans votre navigateur par défaut (comme le serveur React habituel).

Vous y verrez différentes « stories » par défaut. Vous pouvez les supprimer ou les conserver à titre de référence. Comme nous l’avons vu précédemment, un bouton peut avoir plusieurs états, vous pouvez les observer dans Storybook (tous les états ne sont pas mentionnés). Nous écrirons plusieurs « stories » pour le bouton dans la dernière partie de ce tutoriel.

Explorez les différentes sections de Storybook et familiarisez-vous avec celles-ci. Nous en aborderons certaines dans ce tutoriel.

Écrivons notre première « story ».

Test de Storybook

Nous avons maintenant un Storybook fonctionnel avec quelques exemples.

  • Créez un dossier appelé « Button » dans le dossier « src ».
  • Créez les fichiers « Button.jsx », « Button.css » et « constants.js ».
  • Copiez les extraits de code ci-dessous dans les fichiers correspondants.

Button.jsx

import React, { Component } from "react";
import PropTypes from "prop-types";

import "./Button.css";

import { buttonTypes, buttonVariants, buttonSizes } from "./constants";

class Button extends Component {
    static defaultProps = {
        isDisabled: false,
        type: "filled",
        variant: "oval",
        size: "medium",
        backgroundColor: "#1ea7fd",
        textColor: "#ffffff",
    };

    static buttonTypes = buttonTypes;
    static buttonVariants = buttonVariants;
    static buttonSizes = buttonSizes;

    renderButton = () => {
        const {
            text,
            isDisabled,
            type,
            variant,
            size,
            backgroundColor,
            textColor,
            onClick,
        } = this.props;
        return (
            <button
                onClick={onClick}
                className={`default ${variant} ${size} ${
                    isDisabled ? "disabled" : ""
                }`}
                style={
                    type === buttonTypes.outline
                        ? {
                              border: `1px solid ${backgroundColor}`,
                              color: "#000000",
                              backgroundColor: "transparent",
                          }
                        : {
                              backgroundColor: `${backgroundColor}`,
                              border: `1px solid ${backgroundColor}`,
                              color: textColor,
                          }
                }
                disabled={isDisabled}
            >
                {text}
            </button>
        );
    };

    render() {
        return this.renderButton();
    }
}

Button.propTypes = {
    text: PropTypes.string,
    isDisabled: PropTypes.bool,
    type: PropTypes.oneOf([buttonTypes.outline, buttonTypes.filled]),
    variant: PropTypes.oneOf([buttonVariants.oval, buttonVariants.rectangular]),
    size: PropTypes.oneOf([
        buttonSizes.small,
        buttonSizes.medium,
        buttonSizes.large,
    ]),
    backgroundColor: PropTypes.string,
    textColor: PropTypes.string,
    onClick: PropTypes.func,
};

export { Button };

Button.css

.default {
    border: none;
    cursor: pointer;
    background-color: transparent;
}

.default:focus {
    outline: none;
}

.disabled {
    opacity: 0.75;
    cursor: not-allowed;
}
.small {
    font-size: 12px;
    padding: 4px 8px;
}

.medium {
    font-size: 14px;
    padding: 8px 12px;
}

.large {
    font-size: 16px;
    padding: 12px 16px;
}

.oval {
    border-radius: 4px;
}

.rectangular {
    border-radius: 0;
}

constants.js

export const buttonTypes = {
    outline: "outline",
    filled: "filled",
};

export const buttonVariants = {
    oval: "oval",
    rectangular: "rectangular",
};

export const buttonSizes = {
    small: "small",
    medium: "medium",
    large: "large",
};

Que contient ce code ?

Nous avons créé un composant « Button » générique, adaptable à diverses utilisations. Il possède donc différents états possibles.

Écrivons maintenant notre première « story » en suivant les étapes ci-dessous.

  • Créez un fichier nommé « Button.stories.jsx ».
  • Importez React et votre composant « Button » dans ce fichier.
  • Définissez un titre ou un chemin pour les « stories » de ce composant. Utilisez le code suivant :
export default {
   title: 'common/Button',
}

Ce code placera toutes les « stories » présentes dans ce fichier dans le répertoire « common/Button/ ».

  • Exportez un bouton avec des propriétés minimales, comme ceci :
export const defaultButton = () => (
    <Button text="Default Button" onClick={() => {}} />
);

Notre première « story » est terminée. Lancez Storybook avec la commande suivante et observez le résultat.

npm run storybook

Nous écrirons d’autres « stories » par la suite, ne vous inquiétez pas.

Pourquoi Storybook est-il utile pour le développement Frontend ?

Quel est l’avantage majeur de l’utilisation de Storybook ?

Imaginons une équipe de 10 personnes. Nous devons vérifier les composants communs que chacun a développés pour le projet en cours.

Comment faire ?

Il faudrait aller vérifier chaque composant un par un. Cela prend beaucoup de temps et n’est pas la méthode idéale. C’est là que Storybook intervient.

Comment Storybook nous aide-t-il dans cette situation ?

Nous pouvons écrire des « stories » pour les composants communs (tous les composants d’interface utilisateur) à l’aide de Storybook. Ainsi, lorsqu’un membre de l’équipe souhaite vérifier les composants des autres, il lui suffit de lancer le serveur Storybook et d’y voir tous les composants d’interface utilisateur, comme nous l’avons vu précédemment.

Il est possible de faire bien plus avec les composants rendus dans Storybook. Storybook dispose d’un concept appelé « Addons » qui ajoute de nouvelles possibilités à nos « stories ».

Par exemple, si nous souhaitons tester la réactivité des composants directement dans Storybook, nous pouvons utiliser un « addon » appelé « Viewport ». Nous en apprendrons davantage sur les « addons » dans les sections à venir.

Manipulation de Storybook

Dans cette partie, nous allons écrire plusieurs « stories » qui définissent divers états de notre composant « Button ».

Écrire des « stories » n’est pas compliqué. Une « story » décrit un état d’un composant. Si vous regardez les propriétés d’un composant, vous comprendrez facilement les différentes manières de l’utiliser.

Écrivons quelques « stories » en attribuant des propriétés optionnelles.

export const largeButton = () => (
    <Button text="Large Button" onClick={() => {}} size="large" />
);
export const outlineSmallButton = () => (
    <Button
        text="Outline Small Button"
        onClick={() => {}}
        size="small"
        type="outline"
    />
);
export const rectangularLargeButton = () => (
    <Button
        text="Rectangular Large Button"
        onClick={() => {}}
        size="large"
        variant="rectangular"
    />
);


export const disabledButton = () => (
    <Button text="Disabled Button" onClick={() => {}} isDisabled={true} />
);


export const warningButton = () => (
    <Button
        text="Warning Button"
        onClick={() => {}}
        backgroundColor="orange"
    />
);

Ces trois « stories » définissent des cas d’utilisation différents pour notre composant « Button ». À vous maintenant d’ajouter d’autres cas possibles, tels que « disabledSamllRectangularButton », « dangerButton », « successDisabledButton », etc.

Je ne fournirai pas le code pour ces exemples. C’est en les écrivant vous-même que vous les comprendrez. Voici le code complet des « stories » que nous avons écrites jusqu’à présent :

import React from "react";

import { Button } from "./Button";

export default {
    title: "src/common/Button",
};

export const defaultButton = () => (
    <Button text="Default Button" onClick={() => {}} />
);

export const largeButton = () => (
    <Button text="Large Button" onClick={() => {}} size="large" />
);

export const outlineSmallButton = () => (
    <Button
        text="Outline Small Button"
        onClick={() => {}}
        size="small"
        type="outline"
    />
);

export const rectangularLargeButton = () => (
    <Button
        text="Rectangular Large Button"
        onClick={() => {}}
        size="large"
        variant="rectangular"
    />
);

export const disabledButton = () => (
    <Button text="Disabled Button" onClick={() => {}} isDisabled={true} />
);

export const warningButton = () => (
    <Button
        text="Disabled Button"
        onClick={() => {}}
        backgroundColor="orange"
    />
);

Vous maîtrisez désormais la création de « stories » pour un composant.

Passons à la section suivante où nous découvrirons les « addons » et leur impact sur nos « stories ».

« Addons » de Storybook

Plusieurs « addons » sont disponibles par défaut. Dans cette section, nous allons explorer les plus utiles pour notre développement.

Dynamisons nos « stories » de boutons.

Les contrôles

Les contrôles ajoutent la possibilité de modifier les propriétés d’un composant directement dans Storybook. Pour notre composant « Button », nous pouvons ajouter des contrôles afin de modifier ses différentes caractéristiques.

Supposons que nous devons trouver la meilleure couleur de fond pour un bouton. Il faudrait tester plusieurs couleurs une par une. Au lieu de cela, nous pouvons ajouter un contrôle qui permet de choisir une couleur différente dans Storybook et tester immédiatement le rendu.

Voyons comment ajouter des contrôles à nos « stories » de boutons.

Tout d’abord, définissons toutes les propriétés sous le titre, comme ceci :

export default {
    title: "src/common/Button",
    argTypes: {
        text: { control: "text" },
        backgroundColor: { control: "color" },
        isDisabled: { control: "boolean" },
        size: {
            control: { type: "select", options: ["small", "medium", "large"] },
        },
        type: {
            control: { type: "select", options: ["filled", "outline"] },
        },
        variant: {
            control: { type: "select", options: ["oval", "rectangular"] },
        },
    },
};

Ensuite, séparons les propriétés du composant et donnons-leur les arguments suivants :

export const outlineSmallButton = (args) => (
    <Button {...args} onClick={() => {}} />
);
outlineSmallButton.args = {
    text: "Outline Small Button",
    size: "small",
    type: "outline",
};

Vous verrez les contrôles en bas de la fenêtre de prévisualisation du composant.

L’onglet des contrôles se trouve en bas de la fenêtre de prévisualisation. N’hésitez pas à expérimenter.

Mettez à jour toutes les « stories » comme ci-dessus. Il s’agit ici de connaître la syntaxe des « addons » de Storybook. Dans « argTypes », nous avons utilisé divers types de contrôles. Vous trouverez tous les contrôles disponibles dans Storybook ici.

Les « stories » de boutons mises à jour se présenteront comme suit :

import React from "react";

import { Button } from "./Button";

export default {
    title: "src/common/Button",
    argTypes: {
        text: { control: "text" },
        backgroundColor: { control: "color" },
        isDisabled: { control: "boolean" },
        size: {
            control: { type: "select", options: ["small", "medium", "large"] },
        },
        type: {
            control: { type: "select", options: ["filled", "outline"] },
        },
        variant: {
            control: { type: "select", options: ["oval", "rectangular"] },
        },
    },
};

export const defaultButton = (args) => <Button {...args} onClick={() => {}} />;
defaultButton.args = {
    text: "Default Button",
};

export const largeButton = (args) => (
    <Button {...args} onClick={() => {}} size="large" />
);
largeButton.args = {
    text: "Large Button",
};

export const outlineSmallButton = (args) => (
    <Button {...args} onClick={() => {}} />
);
outlineSmallButton.args = {
    text: "Outline Small Button",
    size: "small",
    type: "outline",
};

export const rectangularLargeButton = (args) => (
    <Button {...args} onClick={() => {}} />
);
rectangularLargeButton.args = {
    text: "Rectangular Large Button",
    size: "large",
    variant: "rectangular",
};

export const disabledButton = (args) => <Button {...args} onClick={() => {}} />;
disabledButton.args = {
    text: "Disabled Button",
    isDisabled: true,
};

export const warningButton = (args) => <Button {...args} onClick={() => {}} />;
warningButton.args = {
    text: "Warning Button",
    backgroundColor: "orange",
};

Actions

Les actions correspondent aux événements en JavaScript. Un clic sur un bouton est un événement en JavaScript. L’addon « actions » permet de déclencher des actions lors de ces événements.

Grâce aux « actions », nous pouvons vérifier le bon fonctionnement des événements. Par exemple, un bouton désactivé ne doit pas être cliquable et un bouton activé doit réagir au clic. Cela peut être vérifié à l’aide des « actions ».

Voyons comment ajouter une action au clic sur un bouton.

Jusqu’à présent, nous avons attribué une fonction anonyme aux propriétés « onClick ». Nous devons maintenant la mettre à jour.

  • Importez « action » depuis l’addon Storybook en utilisant la commande suivante :
import { action } from "@storybook/addon-actions";
  • Remplacez tous les « () => {} » par la commande suivante :
action("Button is clicked!")

Maintenant, ouvrez Storybook et cliquez sur un bouton. Vous verrez le message affiché dans l’onglet « actions », à côté de l’onglet « contrôles ». Le message ne s’affichera pas si vous cliquez sur un bouton désactivé.

Nous pouvons utiliser les « actions » pour divers événements, tels que « onChange », « onMouseOver », « onMouseOut », etc., afin de nous assurer de leur bon fonctionnement. Essayez d’implémenter la même chose avec « onChange » pour un champ de saisie.

Vous trouverez plus d’informations sur les « actions » ici.

Arrière-plan

Il est possible de changer l’arrière-plan de la fenêtre de prévisualisation à l’aide de l’addon « backgrounds », sans avoir à écrire une seule ligne de code. Observez le gif ci-dessous :

Fenêtre

Nous pouvons aussi tester la réactivité de nos composants dans Storybook. Le gif ci-dessous illustre les différentes options de la fenêtre :

Documentation

Il est possible de documenter nos composants dans Storybook grâce à l’addon « docs ». C’est particulièrement utile lorsque l’on travaille en équipe. Les autres développeurs pourront consulter directement la documentation du composant. Cela fait gagner un temps précieux.

Dans la fenêtre de prévisualisation des composants Storybook, vous trouverez l’onglet « Docs » en haut à droite de l’onglet « Canvas ». Il contient la documentation de toutes les « stories » d’un composant. Il faut utiliser le fichier « Button.stories.mdx » si nous souhaitons documenter un composant incluant à la fois le rendu du « markdown » et du composant. Nous ajoutons simplement du code « markdown » avec les « stories » des composants.

Écrivons une documentation pour nos « stories ». Le code contient du « markdown » et le rendu des composants. Il s’agit simplement de connaître la syntaxe. Vous la maîtriserez rapidement.

Voici le code du fichier « Button.stories.mdx » :

<!--- Button.stories.mdx -->

import {
    Meta,
    Story,
    Preview,
    ArgsTable
} from '@storybook/addon-docs/blocks';

import { Button } from './Button';

<Meta title="MDX/Button" component={Button} />

# Button Documentation

With `MDX` we can define a story for `Button` right in the middle of our
Markdown documentation.

<ArgsTable of={Button} />

export const Template = (args) => <Button {...args} />

## Default Button
We can write the documentation related to the Default Button
<Preview>
    <Story name="Default Button" args={{
        text: 'Default Button'
    }}>
    {Template.bind({})}
   </Story>
</Preview>

## Large Button
We are writing sample docs for two stories, you can write rest of them
<Preview>
    <Story name="Large Button" args={{
        text: "Large Button",
        }}>
        {Template.bind({})}
    </Story>
</Preview>

Pour plus d’informations sur les composants de documentation, consultez cette page.

Vous trouverez plus d’informations sur les « addons » ici.

Conclusion

J’espère que vous avez apprécié ce tutoriel et que vous avez bien compris le fonctionnement de Storybook. Utilisez-le efficacement au sein de votre équipe pour optimiser votre productivité.

Vous débutez avec React ? Consultez ces ressources d’apprentissage.

Bon codage 🙂