https://github.com/jondot/storybook-cartesian
Automatically generate stories for all of your component variants
https://github.com/jondot/storybook-cartesian
react storybook testing
Last synced: 11 months ago
JSON representation
Automatically generate stories for all of your component variants
- Host: GitHub
- URL: https://github.com/jondot/storybook-cartesian
- Owner: jondot
- License: mit
- Created: 2018-09-29T13:15:15.000Z (over 7 years ago)
- Default Branch: master
- Last Pushed: 2019-01-09T09:06:49.000Z (over 7 years ago)
- Last Synced: 2025-07-01T20:51:53.398Z (12 months ago)
- Topics: react, storybook, testing
- Language: TypeScript
- Homepage:
- Size: 1.18 MB
- Stars: 63
- Watchers: 3
- Forks: 7
- Open Issues: 2
-
Metadata Files:
- Readme: README.md
- License: LICENSE.txt
Awesome Lists containing this project
README

## Storybook Cartesian
Automatically generate stories for all of your component variants.

See more about this example in [examples/app](examples/app).
- [Storybook Cartesian](#storybook-cartesian)
- [Quick Start](#quick-start)
- [Basics](#basics)
- [Extras](#extras)
- [Applying Stories with Premade Components](#applying-stories-with-premade-components)
- [Premade title renderers](#premade-title-renderers)
- [Beautiful names for variants](#beautiful-names-for-variants)
- [Validating Variants](#validating-variants)
- [Contributing](#contributing)
- [Thanks](#thanks)
- [Copyright](#copyright)
## Quick Start
Install:
```
$ yarn add --dev storybook-cartesian
```
To integrate in your own project, add to your stories:
```javascript
import cartesian from 'storybook-cartesian'
cartesian(storiesOf('Button/Cartesian', module))
.add(() => ({
colors: [
{ bg: '#FF5630', fg: '#FFBDAD' },
{ bg: '#4C9AFF', fg: '#B3D4FF' }
],
text: ['Click Me', '', '你好']
}),
props =>
{props.text}
)
```
## Basics
The general structure for `cartesian` is this:
```javscript
cartesian()
.add(
,
,
{
renderTitle: ,
valid: ,
apply:
}
)
```
Which gets you this kind of story layout _generated automatically_ (for now the last "All/all variants" is a story discussed in [Advanced](#advanced)):

Your `seed` function is responsible to generate content in the form of:
```javascript
// if this is a sample of your props:
const props = {
one: "hello",
two: "foobar"
check: true
}
// then this is your seed function:
const seedfn = ()=>({
one: ["hello", "another"],
two: ["foobar"]
check: [true, false]
})
```
If you want to have just a selection of props be cartesian you can use the special `choice` function:
```js
import cartesian, { choice } from 'cartesian'
const seedfn = ()=>({
one: "rabbit",
two: "rabbit, rabbit",
check: choice(true, false)
})
```
This will create a special data strucure which tells `cartesian` to create these combinations:
```js
[{
one: "rabbit",
two: "rabbit, rabbit",
check: true
},{
one: "rabbit",
two: "rabbit, rabbit",
check: false
}]
```
Your `titleRender` function gets an instance of your props and returns a string:
```javascript
const renderTitle = props => `${props.one} / ${props.check}`
```
Your `storyRender` function gets an instance of your props and returns a component:
```jsx
const componentRender = props =>
```
And to compose all of these with `cartesian` we can now do:
```javascript
cartesian(storiesOf('Button/Cartesian'))
.add(
seedfn,
componentRender,
{ renderTitle }
)
```
## Extras
### Applying Stories with Premade Components
You can showcase all variants in two ways with one of the premade components:
* `Tiles` - showcase variants as tiles which fill up the screen (many components on rows and columns).
* `Rows` - same thing, just one component per row.
And you have a helper function `applyWith(title, component)` that takes a title and one of these components, and generates your stories `apply` function.
```jsx
import { Tiles, applyWith } from 'storybook-cartesian/react'
cartesian(storiesOf('Button/Cartesian/applyWith(Tiles)', module))
.add(() => ({
colors: [{ bg: '#FF5630', fg: '#FFBDAD' }, { bg: '#4C9AFF', fg: '#B3D4FF' }],
text: ['Click Me', '', '你好']
}),
props => {props.text},
{
renderTitle: titles.renderPropNames(),
apply: applyWith("everything!", Tiles)
}
)
```
And the result:

You can also use any of the individual components on their own:
```jsx
import { Rows } from 'storybook-cartesian/react'
cartesian(storiesOf('Button/Cartesian/Tiles', module))
.add(() => ({
colors: [{ bg: '#FF5630', fg: '#FFBDAD' }, { bg: '#4C9AFF', fg: '#B3D4FF' }],
text: ['Click Me', '', '你好']
}),
props => {props.text},
{
renderTitle: titles.renderPropNames(),
apply: (stories, candidates) => {
stories.add('all variants', () => )
}
}
)
```
### Premade title renderers
You can pick a title renderer from a premade collection:
* `renderCheckSignIfExists` - renders the prop name and a 'check' sign if it exists, 'x' if missing
* `renderPropNames` - renders just the prop names given
* `renderProps` - render a `prop=value` format
You can use one of these like so:
```js
import cartesian, { titles } from 'cartesian'
cartesian(storiesOf('Button/Cartesian'))
.add(
seedfn,
componentRender,
{ renderTitle: titles.renderCheckSignsIfExists() }
)
```
Which produces the following title with `props = { oneProp: null, twoProp: 2}`:
`x oneProp | ✓ twoProp`
There are more renderers that you can explore (also - happy to get PRs with more!).
### Beautiful names for variants
If you'd like prop values to have logical names, try `renderWithLegend`:
```js
import cartesian, { renderWithLegend } from 'cartesian'
const complex = { foo:1, bar: 2 }
complex.toString = () => 'complex-1'
const titleWithLegend = renderWithLegend({
'#FF5630': 'primary',
'#FFBDAD': 'secondary',
'#4C9AFF': 'primary-opt',
'#B3D4FF': 'secondary-opt',
'Click Me': 'english',
[complex]: 'complex object',
'': 'empty',
'你好': 'chinese'
})
cartesian(storiesOf('Button/Cartesian (legend)', module))
.add(() => ({
colors: [{ bg: '#FF5630', fg: '#FFBDAD' }, { bg: '#4C9AFF', fg: '#B3D4FF' }],
text: ['Click Me', '', '你好']
}),
props => {props.text},
{
renderTitle: titleWithLegend(props => `"${props.text}" ${props.colors.bg + '/' + props.colors.fg}`),
}
)
```
`renderWithLegend` takes a legend dict, that maps actual prop values to the ones you give in the legend.
Then, it takes a normal renderTitle function that you supply, and it will make sure prop values will be legend values.
If you want just a top level legend translation (not going into all values in a data structure) use `renderWithLegendFlat`.
### Validating Variants
Some times, not all prop combinations make sense. For example if you have an `isLoading` and a `results` props it doesn't make
sense to have both `true` and `results` populated:
```javascript
// doesn't make sense
```
For this, we have an `valid` function that we can add, and the following will filter out this invalid combination:
```javascript
cartesian(storiesOf('Button/Cartesian'))
.add(
seedfn,
componentRender,
{
renderTitle,
valid: props => !(props.isLoading && props.results)
}
)
```
Some other times you might want to customize how you add stories. For example, let's say you want just one story to contain all cartesian product items.

For this, we have another optional function:
```javascript
const allVariantsInOne = (stories, variants)=>{
const story = variants.map(c=>(
{c.title}
{c.story}
))
stories.add('all variants', ()=> story)
}
cartesian(storiesOf('Button/Cartesian'))
.add(
seedfn,
componentRender,
{ apply: allVariantsInOne }
)
```
## Contributing
Fork, implement, add tests, pull request, get my everlasting thanks and a respectable place here :).
### Thanks
To all [Contributors](https://github.com/jondot/storybook-cartesian/graphs/contributors) - you make this happen, thanks!
## Copyright
Copyright (c) 2018 [Dotan Nahum](http://gplus.to/dotan) [@jondot](http://twitter.com/jondot). See [LICENSE](LICENSE.txt) for further details.