Giter VIP home page Giter VIP logo

storybook-cartesian's Introduction

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.

storybook-cartesian's People

Contributors

jondot avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.