ESM Stories
You can create interactive component examples (aka stories) in pure Javascript or Typescript.
To do so, you need to create a default export with at least a title attribute and at least one named export, which is considered a story.
Our ESM story format is loosely compatible with Storybook's CSF with a few properties in common (ie title, component, decorators). However, our ESM story format is fully extensible beyond just using a select few hard-coded props (ie parameters).
The ESM format has the following advantages:
- The documentation, testing, and design pages are created automatically.
- Since ESM are regular javascript or typescript files, you get full support from your IDE and linting tools.
The ESM format has the following disadvantages:
- For control over the documentation pages, you need to use advanced features such as replacing the pages and tabs.
- You can not easily embed custom documentation into the pages
By default, your ESM story files should end with
.(story|stories).(js|jsx|ts|tsx)
. An example of a valid ESM typescript file would be my-story.stories.tsx
;All the document metadata is exported as the default module export (there can be only one default export per es module). For example the document(page) title, component, etc. are defined as properties:
export default {title: 'My Story'}
more: mdxjs
The stories (live examples of your components) are defined as named exports
export const myStory = <Button>some text</Button>;
You can attach properties to you stories either directly:
export const myStory = <Button>some text</Button>;myStory.description = 'A button example with some text'
Or using the
story
namespace:export const myStory = <Button>some text</Button>;myStory.story = {description: 'A button example with some text',...};
Using story templates you can reduce the amount of repetitive code in your ESM stories documentation. This is usually needed when you want to create separate stories for the various state values of your components.
Component-controls supports two ways of creating story templates:
- Use the document
template
field.
This is a more compact syntax, where you can assign the template to the document object.
import React from 'react';import { Document } from '@component-controls/core';import { ButtonProps, Button } from '../../components/button-props';export default {title: 'Introduction/Template doc',component: Button,template: props => <Button {...props} />,} as Document<ButtonProps>;export const John = {controls: {children: 'john',type: 'reset',},};
- Bind stories to a template
This syntax is slightly more verbose and is compatible with Storybook 6
import { Example, Document } from '@component-controls/core';import { ButtonProps, Button } from '../components/Button';export default {title: 'Introduction/Template bind',component: Button,smartControls: false,} as Document;const ComponentTemplate: Example<ButtonProps> = props => <Button {...props} />;export const John = ComponentTemplate.bind();John.controls = {children: 'john',type: 'reset',};
You can create multiple stories dynamically from a function To do this, all you need is to add
dynamic = true
flag to the exported story:import React from 'react';import { theme } from '@component-controls/components';export default {title: Components/Button',};export const buttonColors = () => {return Object.keys(theme.colors).filter(color => typeof theme.colors[color] === 'string').map(color => {return {name: color,source: `<Button sx={{ bg: '${color}'}}>Color ${color}: ${theme.colors[color]}</Button>`,renderFn: () => (<Buttonsx={{ bg: color }}>{`Color ${color}: ${theme.colors[color]}`}</Button>),};});};buttonColors.dynamic = true;
dynamic stories are created at run-time (when the site loads in the browser), while in the case of gatsby, the static page routes are created under anodejs
process. This makes it so all the newly created dynamic stories forgatsby
will reside under the URL of the dynamic parent story, with astory
parameter:https://xxx/api/components-button--button-colors/?story=Background
Sometimes, you might need to fetch data (or execute some other async activity) in your stories or decorators, and component-controls provides a custom
useAsync
hook for that purpose:import { useAsync } from '@component-controls/core';export const asyncFetch = () => {// async function to fetch dataconst fetchData = async () => {const response = await fetch('//dummy.restapiexample.com/api/v1/employee/1');const { data } = await response.json();return data;};const { value: employee } = useAsync(fetchData);return <h2>{`Hello, my name is ${employee ? employee.employee_name : 'loading...'}.`}</h2>;};
Note that you can also fetch data using react's useEffect hooks, without usinguseAsync
.