Plus tôt, nous avons lancé une fonctionnalité clé du Storybook : le robuste écosystème addons. Les addons sont utilisés pour améliorer l'expérience et les workflows des développeurs.
Dans ce chapitre bonus, nous allons voir comment créer notre propre addon. Vous pensez peut-être que l'écrire est une tâche insurmontable, mais en fait, ce n'est pas le cas, nous devons juste prendre quelques étapes pour commencer et nous pourrons commencer à l'écrire.
Mais d'abord, il faut déterminer ce que notre addon va faire.
Pour cet exemple, supposons que notre équipe dispose de certaines design assets qui sont d'une manière ou d'une autre, liées aux autres composants existants de l'UI. En examinant l'UI actuelle de Storybook, il semble que cette relation ne soit pas vraiment apparente. Comment pouvons-nous y remédier?
Nous avons notre objectif, maintenant définissons les fonctionnalités que notre addon va prendre en charge:
Nous joindrons la liste des ressources aux story avec paramètres, une fonctionnalité de Storybook qui nous permet d'ajouter des métadonnées supplémentaires à nos stories.
export default {
title: 'Your component',
decorators: [
/*...*/
],
parameters: {
assets: ['path/to/your/asset.png'],
},
//
};
Nous avons décrit ce que notre addon va faire, il est temps de commencer à travailler dessus.
Dans le dossier racine de votre projet, créez un nouveau fichier appelé .babelrc
avec ce qui suit à l'intérieur :
{
"presets": [
[
"@babel/preset-env",
{
"targets": {
"node": "current"
}
}
],
"@babel/preset-react"
]
}
L'ajout de ce fichier nous permettra d'utiliser les préréglages et les options corrects pour l'addon que nous allons développer.
Ensuite, dans votre dossier .storybook
, créez un nouveau dossier appelé design-addon
et à l'intérieur de celui-ci, un nouveau fichier appelé register.js
.
Et c'est tout! Nous sommes prêts à commencer le développement de notre addon.
.storybook
comme emplacement pour notre addon. La raison en est de maintenir une approche simple et d'éviter de trop le compliquer. Si cet addon devait être transformé en un véritable addon, il serait préférable de le déplacer dans un paquet séparé avec sa propre structure de fichiers et de dossiers.Ajoutez les éléments suivants à votre dossier récemment créé:
//.storybook/design-addon/register.js
import React from 'react';
import { AddonPanel } from '@storybook/components';
import { addons, types } from '@storybook/addons';
addons.register('my/design-addon', () => {
addons.add('design-addon/panel', {
title: 'assets',
type: types.PANEL,
render: ({ active, key }) => (
<AddonPanel active={active} key={key}>
implement
</AddonPanel>
),
});
});
C'est le code typique pour vous aider à démarrer. Nous allons voir ce que fait le code :
Si nous démarrons Storybook à ce stade, nous ne pourrons pas encore voir l'addon. Comme nous l'avons fait précédemment avec l'addon Controls, nous devons enregistrer la nôtre dans le fichier .storybook/main.js
. Il suffit d'ajouter ce qui suit à la liste des addons
déjà existants :
// .storybook/main.js
module.exports = {
stories: ['../src/components/**/*.stories.js'],
addons: [
// same as before
'./design-addon/register.js', // our addon
],
};
Succès! Nous avons ajouté notre nouvel addon à l'UI de Storybook.
Nous avons atteint notre premier objectif. Il est temps de commencer à travailler sur le second.
Pour le mener à bien, nous devons apporter quelques modifications à nos imports et introduire un nouveau composant qui affichera les informations sur les assets.
Apportez les modifications suivantes au fichier addon:
//.storybook/design-addon/register.js
import React, { Fragment } from 'react';
/* same as before */
import { useParameter } from '@storybook/api';
const Content = () => {
const results = useParameter('assets', []); // story's parameter being retrieved here
return (
<Fragment>
{results.length ? (
<ol>
{results.map(i => (
<li>{i}</li>
))}
</ol>
) : null}
</Fragment>
);
};
Nous avons créé le composant, modifié les imports, tout ce qui manque c'est de connecter le composant à notre panel et nous aurons un addon fonctionnel capable d'afficher des informations relatives à nos story.
Votre code devrait ressembler à ce qui suit:
//.storybook/design-addon/register.js
import React, { Fragment } from 'react';
import { AddonPanel } from '@storybook/components';
import { useParameter } from '@storybook/api';
import { addons, types } from '@storybook/addons';
const Content = () => {
const results = useParameter('assets', []); // story's parameter being retrieved here
return (
<Fragment>
{results.length ? (
<ol>
{results.map(i => (
<li>{i}</li>
))}
</ol>
) : null}
</Fragment>
);
};
addons.register('my/design-addon', () => {
addons.add('design-addon/panel', {
title: 'assets',
type: types.PANEL,
render: ({ active, key }) => (
<AddonPanel active={active} key={key}>
<Content />
</AddonPanel>
),
});
});
Remarquez que nous utilisons le useParameter, ce hook pratique nous permettra de lire les informations fournies par l'option paramètres
pour chaque story, qui dans notre cas sera soit un chemin unique vers un assets, soit une liste de chemins. Vous le verrez bientôt entrer en vigueur.
Nous avons connecté toutes les pièces nécessaires. Mais comment pouvons-nous voir si cela fonctionne et affiche quelque chose?
Pour ce faire, nous allons apporter une petite modification au fichier Task.stories.js
et ajouter l'option parameters.
// src/components/Task.stories.js
export default {
component: Task,
title: 'Task',
parameters: {
assets: [
'path/to/your/asset.png',
'path/to/another/asset.png',
'path/to/yet/another/asset.png',
],
},
argTypes: {
/* ...actionsData, */
backgroundColor: { control: 'color' },
},
};
/* comme avant */
Allez-y, redémarrez votre Storybook et sélectionnez le story Task, vous devriez voir quelque chose comme ceci :
À ce stade, nous pouvons voir que l'addon fonctionne comme il le devrait, mais maintenant changeons le composant Contenu
pour afficher réellement ce que nous voulons:
//.storybook/design-addon/register.js
import React, { Fragment } from 'react';
import { AddonPanel } from '@storybook/components';
import { useParameter, useStorybookState } from '@storybook/api';
import { addons, types } from '@storybook/addons';
import { styled } from '@storybook/theming';
const getUrl = input => {
return typeof input === 'string' ? input : input.url;
};
const Iframe = styled.iframe({
width: '100%',
height: '100%',
border: '0 none',
});
const Img = styled.img({
width: '100%',
height: '100%',
border: '0 none',
objectFit: 'contain',
});
const Asset = ({ url }) => {
if (!url) {
return null;
}
if (url.match(/\.(png|gif|jpeg|tiff|svg|anpg|webp)/)) {
// do image viewer
return <Img alt="" src={url} />;
}
return <Iframe title={url} src={url} />;
};
const Content = () => {
// story's parameter being retrieved here
const results = useParameter('assets', []);
// the id of story retrieved from Storybook global state
const { storyId } = useStorybookState();
if (results.length === 0) {
return null;
}
const url = getUrl(results[0]).replace('{id}', storyId);
return (
<Fragment>
<Asset url={url} />
</Fragment>
);
};
Si vous regardez de plus près, vous verrez que nous utilisons le tag style
, ce tag vient du paquet @storybook/theming
. L'utilisation de cette balise nous permettra de personnaliser non seulement le thème de Storybook mais aussi l'UI en fonction de nos besoins. De plus, useStorybookState
, qui est un hook très pratique, nous permet d'accéder à l'état interne de Storybook afin de récupérer n'importe quelle information présente. Dans notre cas, nous l'utilisons pour récupérer uniquement l'id de chaque story créé.
Pour voir réellement les ressources affichées dans notre addon, nous devons les copier dans le dossier public
et ajuster l'option parameters
du story pour refléter ces changements.
Le Storybook prendra en compte ces changements et chargera les ressources, mais pour l'instant, seulement la première.
Revenir sur nos objectifs initiaux :
Nous y sommes presque, il ne reste plus qu'un but à atteindre.
Pour le dernier, nous allons avoir besoin d'une sorte d'état, nous pourrions utiliser le hook useState
de React, ou si nous travaillions avec des composants de classe this.setState()
. Mais à la place, nous allons utiliser le useStorybookState
de Storybook, qui nous donne un moyen de persister l'état de l'addon, et d'éviter de créer une logique supplémentaire pour persister l'état local. Nous utiliserons également un autre élément de l'UI de Storybook, l'ActionBar
, qui nous permettra de passer d'un élément à l'autre.
Nous devons adapter nos imports à nos besoins :
//.storybook/design-addon/register.js
import { useParameter, useStorybookState, useAddonState } from '@storybook/api';
import { AddonPanel, ActionBar } from '@storybook/components';
/* same as before */
Et modifier notre composant Content
, afin que nous puissions passer d'un asset à l'autre:
//.storybook/design-addon/register.js
const Content = () => {
// story's parameter being retrieved here
const results = useParameter('assets', []);
// addon state being persisted here
const [selected, setSelected] = useAddonState('my/design-addon', 0);
// the id of the story retrieved from Storybook global state
const { storyId } = useStorybookState();
if (results.length === 0) {
return null;
}
if (results.length && !results[selected]) {
setSelected(0);
return null;
}
const url = getUrl(results[selected]).replace('{id}', storyId);
return (
<Fragment>
<Asset url={url} />
{results.length > 1 ? (
<ActionBar
actionItems={results.map((i, index) => ({
title: typeof i === 'string' ? `asset #${index + 1}` : i.name,
onClick: () => setSelected(index),
}))}
/>
) : null}
</Fragment>
);
};
Nous avons accompli ce que nous nous étions fixé comme objectif, à savoir créer un addon Storybook pleinement fonctionnel qui affiche les design assets liés aux composants de l'UI.
// .storybook/design-addon/register.js
import React, { Fragment } from 'react';
import { useParameter, useStorybookState, useAddonState } from '@storybook/api';
import { addons, types } from '@storybook/addons';
import { AddonPanel, ActionBar } from '@storybook/components';
import { styled } from '@storybook/theming';
const getUrl = input => {
return typeof input === 'string' ? input : input.url;
};
const Iframe = styled.iframe({
width: '100%',
height: '100%',
border: '0 none',
});
const Img = styled.img({
width: '100%',
height: '100%',
border: '0 none',
objectFit: 'contain',
});
const Asset = ({ url }) => {
if (!url) {
return null;
}
if (url.match(/\.(png|gif|jpeg|tiff|svg|anpg|webp)/)) {
return <Img alt="" src={url} />;
}
return <Iframe title={url} src={url} />;
};
const Content = () => {
const results = useParameter('assets', []); // story's parameter being retrieved here
const [selected, setSelected] = useAddonState('my/design-addon', 0); // addon state being persisted here
const { storyId } = useStorybookState(); // the story«s unique identifier being retrieved from Storybook global state
if (results.length === 0) {
return null;
}
if (results.length && !results[selected]) {
setSelected(0);
return null;
}
const url = getUrl(results[selected]).replace('{id}', storyId);
return (
<Fragment>
<Asset url={url} />
{results.length > 1 ? (
<ActionBar
actionItems={results.map((i, index) => ({
title: typeof i === 'string' ? `asset #${index + 1}` : i.name,
onClick: () => setSelected(index),
}))}
/>
) : null}
</Fragment>
);
};
addons.register('my/design-addon', () => {
addons.add('design-addon/panel', {
title: 'assets',
type: types.PANEL,
render: ({ active, key }) => (
<AddonPanel active={active} key={key}>
<Content />
</AddonPanel>
),
});
});
La prochaine étape logique pour notre addon, serait d'en faire son propre paquet et de permettre sa distribution avec votre équipe et éventuellement avec le reste de la communauté.
Mais cela dépasse le cadre de ce tutoriel. Cet exemple montre comment vous pouvez utiliser l'API de Storybook pour créer votre propre addon personnalisé afin d'améliorer encore votre workflow de développement.
Apprenez à personnaliser davantage votre addon :
Et bien plus encore !
Pour vous aider à démarrer le développement de l'addon, l'équipe de Storybook a mis au point des dev-kits
.
Ces paquets sont des kits de démarrage pour vous aider à commencer à construire vos propres addons.
L'addon que nous venons de créer est basé sur un de ces starter-sets, plus précisément le dev-kit addon-parameters
.
Vous pouvez trouver celui-ci et d'autres ici : https://github.com/storybookjs/storybook/tree/next/dev-kits
D'autres kits de développement seront disponibles à l'avenir.
Les addons sont des ajouts qui vous font gagner du temps, mais il peut être difficile pour les coéquipiers non techniques et les examinateurs de tirer parti de leurs fonctionnalités. Vous ne pouvez pas garantir que les gens utiliseront Storybook sur leur machine locale. C'est pourquoi il peut être très utile de déployer votre Storybook sur un site en ligne où chacun pourra s'y référer. C'est ce que nous allons faire dans le prochain chapitre!