npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

storybook-cartesian

v2.1.1

Published

![](media/cover.png)

Downloads

1,793

Readme

Storybook Cartesian

Automatically generate stories for all of your component variants.

See more about this example in examples/app.

Quick Start

Install:

$ yarn add --dev storybook-cartesian

To integrate in your own project, add to your stories:

import cartesian from 'storybook-cartesian'
cartesian(storiesOf('Button/Cartesian', module))
  .add(() => ({
        colors: [
            { bg: '#FF5630', fg: '#FFBDAD' }, 
            { bg: '#4C9AFF', fg: '#B3D4FF' }
        ],
        text: ['Click Me', '', '你好']
    }),
    props => <Button 
                style={{ 
                    padding: '1em 3em', 
                    border: 'none', 
                    backgroundColor: props.colors.bg, 
                    color: props.colors.fg 
                }}>
                {props.text}
            </Button>
  )

Basics

The general structure for cartesian is this:

cartesian(<stories>)
    .add(
        <seed function>,
        <component renderer>,
        { 
            renderTitle: <title renderer>,
            valid: <valid combination filter (optional)>,
            apply: <story apply function (optional)>
        }
    )

Which gets you this kind of story layout generated automatically (for now the last "All/all variants" is a story discussed in Advanced):

Your seed function is responsible to generate content in the form of:

// 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:

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:

[{
    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:

const renderTitle = props => `${props.one} / ${props.check}`

Your storyRender function gets an instance of your props and returns a component:

const componentRender = props => <Button {...props} />

And to compose all of these with cartesian we can now do:

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.

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 => <Button style={{ padding: '1em 3em', border: 'none', backgroundColor: props.colors.bg, color: props.colors.fg }}>{props.text}</Button>,
    { 
      renderTitle: titles.renderPropNames(),
      apply: applyWith("everything!", Tiles)
    }
  )

And the result:

You can also use any of the individual components on their own:

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 => <Button style={{ padding: '1em 3em', border: 'none', backgroundColor: props.colors.bg, color: props.colors.fg }}>{props.text}</Button>,
    { 
      renderTitle: titles.renderPropNames(),
      apply: (stories, candidates) => {
        stories.add('all variants', () => <Rows items={candidates}/>)
      }
    }
  )

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:

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:

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 => <Button style={{ padding: '1em 3em', border: 'none', backgroundColor: props.colors.bg, color: props.colors.fg }}>{props.text}</Button>,
    {
        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:

// doesn't make sense
<SearchResults isLoading={true} results={['hello', 'world']}>

For this, we have an valid function that we can add, and the following will filter out this invalid combination:

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:

const allVariantsInOne = (stories, variants)=>{
    const story = variants.map(c=>(
        <div>
            <div>{c.title}</div>
            <div>{c.story}</div>
        </div>))
    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 - you make this happen, thanks!

Copyright

Copyright (c) 2018 Dotan Nahum @jondot. See LICENSE for further details.