In the last days in my team we were moving from a custom "dev ui" written in angular to storybook. During the process we faced several challenges that I would like to share with you here.
Our nx angular workspace which structure looks similar to the next one:
|-apps | |-app-a | |-... |-libs | |-context-a | | |-domain | | | |-application | | | |-entities | | | |-infrastructure | | |-feature-a | | |-shared-ui | |-other-context | |-shared | | |-ui-appearance | | | |-src | | | | |-lib | | | | | |- theming (angular material based) | | | | | | |-... (mixins | variables | overrides ...) | | | | | | |- themes | | | | | | | |- _dark.scss | | | | | | | |- _light.scss | | | | | | | |- main.scss (light + dark setup) | | | | | |- ui-module-n (forms | dialogs | layout ... ) | | |- utils-a ...
We wanted storybook mainly for covering components in "ui-appearance" library which is "buildable" & "publishable".
So we followed official documentation in order to add storybook capaibilities to our nx workspace.
npm i -D @nrwl/storybook
Then, we run the command for configuring the storybook for appearance library.
npm run nx g @nrwl/angular:storybook-configuration shared-ui-appearance
Right after we run storybook for that library with the next command
npm run nx run shared-ui-appearance:storybook
And ... voila!
We quickly noticed we wanted to add storybook to other shared libraries , specially for all of them with directives and pipes in order to add more documentation.
We repeat the process and we everything worked again like charm!! Besides the fact we had to run the storybook run command invidually 😅. Fourtunately, Nx guys did upload a video suggesting a workaround for having all libraries stories working together
To be honnest we did not found this video while working in our own solution 😅 and we spent some time to figure how to include all the story files.
Our solution uses an app called storybook instead of a library but in the bassis is the same solution. We also we did removed the configuration for every library since the global one was already including all stories. That allow us forget about adding the storybook configuration boilerplate* for every new library 🚀.
|-apps | |-app-a | |-**storybook** | |-... |-libs | |-context-a | | |-domain | | | |-application | | | |-entities | | | |-infrastructure | | |-feature-a | | |-shared-ui | |-other-context | |-shared | | |-ui-appearance | | | |-src | | | | |-lib | | | | | |- theming (angular material based) | | | | | | |-... (mixins | variables | overrides ...) | | | | | | |- themes | | | | | | | |- _dark.scss | | | | | | | |- _light.scss | | | | | | | |- main.scss (light + dark setup) | | | | | |- ui-module-n (forms | dialogs | layout ... ) | | |- utils-a ...
Boilerplate * global styles, addons ...
Our themes are actived by adding data-theme
attribute to the body
element tag. Even the light theme is not applied by default unless this attribute is set to light
.
Well in our app the ThemingService
will set the default automatically.
We decided to create our own addon
. From all addon types we took the toolbar one .
One of the problems we faced, is about JSX
support in typescript files. Since storybook is mainly written in react, our addons would be like react components.
For example the official addon docs :
import React, { useCallback } from 'react';
import { useGlobals } from '@storybook/api';
import { Icons, IconButton } from '@storybook/components';
import { TOOL_ID } from './constants';
export const Tool = () => {
const [{ myAddon }, updateGlobals] = useGlobals();
const toggleMyTool = useCallback(
() =>
updateGlobals({
myAddon: !myAddon,
}),
[myAddon]
);
return (
<IconButton
key={TOOL_ID}
active={myAddon}
title="Apply outlines to the preview"
onClick={toggleMyTool}
>
<Icons icon="outline" />
</IconButton>
);
};
We would need to enable the jsx support for typescript, which is currently officially supported and described in the typescript documentation:
base.tsconfig.json
"compilationOptions": {
"jsx": "react"
}
But one of the cool things in react world is that every component is a function. So, we could convert the return statement to a function call like that:
...
return IconButton(...args);
...