{"id":19779899,"url":"https://github.com/jondot/storybook-cartesian","last_synced_at":"2025-07-22T20:05:37.580Z","repository":{"id":57371515,"uuid":"150865772","full_name":"jondot/storybook-cartesian","owner":"jondot","description":"Automatically generate stories for all of your component variants","archived":false,"fork":false,"pushed_at":"2019-01-09T09:06:49.000Z","size":1239,"stargazers_count":63,"open_issues_count":2,"forks_count":7,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-07-01T20:51:53.398Z","etag":null,"topics":["react","storybook","testing"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jondot.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-09-29T13:15:15.000Z","updated_at":"2024-06-18T20:01:14.000Z","dependencies_parsed_at":"2022-09-16T10:10:17.727Z","dependency_job_id":null,"html_url":"https://github.com/jondot/storybook-cartesian","commit_stats":null,"previous_names":[],"tags_count":15,"template":false,"template_full_name":null,"purl":"pkg:github/jondot/storybook-cartesian","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jondot%2Fstorybook-cartesian","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jondot%2Fstorybook-cartesian/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jondot%2Fstorybook-cartesian/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jondot%2Fstorybook-cartesian/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jondot","download_url":"https://codeload.github.com/jondot/storybook-cartesian/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jondot%2Fstorybook-cartesian/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":266563915,"owners_count":23948689,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-07-22T02:00:09.085Z","response_time":66,"last_error":null,"robots_txt_status":null,"robots_txt_updated_at":null,"robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["react","storybook","testing"],"created_at":"2024-11-12T05:38:03.435Z","updated_at":"2025-07-22T20:05:37.554Z","avatar_url":"https://github.com/jondot.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"![](media/cover.png)\n\n## Storybook Cartesian\n\nAutomatically generate stories for all of your component variants.\n\n![](media/demo.gif)\n\nSee more about this example in [examples/app](examples/app).\n\n\n\u003c!-- START doctoc generated TOC please keep comment here to allow auto update --\u003e\n\u003c!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --\u003e\n\n\n- [Storybook Cartesian](#storybook-cartesian)\n- [Quick Start](#quick-start)\n- [Basics](#basics)\n- [Extras](#extras)\n  - [Applying Stories with Premade Components](#applying-stories-with-premade-components)\n  - [Premade title renderers](#premade-title-renderers)\n  - [Beautiful names for variants](#beautiful-names-for-variants)\n  - [Validating Variants](#validating-variants)\n- [Contributing](#contributing)\n  - [Thanks](#thanks)\n- [Copyright](#copyright)\n\n\u003c!-- END doctoc generated TOC please keep comment here to allow auto update --\u003e\n\n## Quick Start\n\nInstall:\n\n```\n$ yarn add --dev storybook-cartesian\n```\n\n\nTo integrate in your own project, add to your stories:\n\n```javascript\nimport cartesian from 'storybook-cartesian'\ncartesian(storiesOf('Button/Cartesian', module))\n  .add(() =\u003e ({\n        colors: [\n            { bg: '#FF5630', fg: '#FFBDAD' }, \n            { bg: '#4C9AFF', fg: '#B3D4FF' }\n        ],\n        text: ['Click Me', '', '你好']\n    }),\n    props =\u003e \u003cButton \n                style={{ \n                    padding: '1em 3em', \n                    border: 'none', \n                    backgroundColor: props.colors.bg, \n                    color: props.colors.fg \n                }}\u003e\n                {props.text}\n            \u003c/Button\u003e\n  )\n```\n\n## Basics\n\nThe general structure for `cartesian` is this:\n\n```javscript\ncartesian(\u003cstories\u003e)\n    .add(\n        \u003cseed function\u003e,\n        \u003ccomponent renderer\u003e,\n        { \n            renderTitle: \u003ctitle renderer\u003e,\n            valid: \u003cvalid combination filter (optional)\u003e,\n            apply: \u003cstory apply function (optional)\u003e\n        }\n    )\n```\n\nWhich gets you this kind of story layout _generated automatically_ (for now the last \"All/all variants\" is a story discussed in [Advanced](#advanced)):\n\n![](media/stories.png)\n\nYour `seed` function is responsible to generate content in the form of:\n\n```javascript\n// if this is a sample of your props:\nconst props = {\n    one: \"hello\",\n    two: \"foobar\"\n    check: true\n}\n\n// then this is your seed function:\nconst seedfn = ()=\u003e({\n    one: [\"hello\", \"another\"],\n    two: [\"foobar\"]\n    check: [true, false]\n})\n```\n\nIf you want to have just a selection of props be cartesian you can use the special `choice` function:\n\n```js\nimport cartesian, { choice } from 'cartesian'\n\nconst seedfn = ()=\u003e({\n    one: \"rabbit\",\n    two: \"rabbit, rabbit\",\n    check: choice(true, false)\n})\n```\n\nThis will create a special data strucure which tells `cartesian` to create these combinations:\n\n```js\n[{\n    one: \"rabbit\",\n    two: \"rabbit, rabbit\",\n    check: true\n},{\n    one: \"rabbit\",\n    two: \"rabbit, rabbit\",\n    check: false\n}]\n```\n\n\nYour `titleRender` function gets an instance of your props and returns a string:\n\n```javascript\nconst renderTitle = props =\u003e `${props.one} / ${props.check}`\n```\n\nYour `storyRender` function gets an instance of your props and returns a component:\n\n```jsx\nconst componentRender = props =\u003e \u003cButton {...props} /\u003e\n```\n\nAnd to compose all of these with `cartesian` we can now do:\n\n```javascript\ncartesian(storiesOf('Button/Cartesian'))\n    .add(\n        seedfn,\n        componentRender,\n        { renderTitle }\n    )\n```\n\n\n## Extras\n\n### Applying Stories with Premade Components\n\nYou can showcase all variants in two ways with one of the premade components:\n\n* `Tiles` - showcase variants as tiles which fill up the screen (many components on rows and columns).\n* `Rows` - same thing, just one component per row.\n\nAnd you have a helper function `applyWith(title, component)` that takes a title and one of these components, and generates your stories `apply` function.\n\n\n```jsx\nimport { Tiles, applyWith }  from 'storybook-cartesian/react'\n\ncartesian(storiesOf('Button/Cartesian/applyWith(Tiles)', module))\n  .add(() =\u003e ({\n      colors: [{ bg: '#FF5630', fg: '#FFBDAD' }, { bg: '#4C9AFF', fg: '#B3D4FF' }],\n      text: ['Click Me', '', '你好']\n    }),\n    props =\u003e \u003cButton style={{ padding: '1em 3em', border: 'none', backgroundColor: props.colors.bg, color: props.colors.fg }}\u003e{props.text}\u003c/Button\u003e,\n    { \n      renderTitle: titles.renderPropNames(),\n      apply: applyWith(\"everything!\", Tiles)\n    }\n  )\n```\n\nAnd the result:\n\n![](media/tiles.png)\n\nYou can also use any of the individual components on their own:\n\n```jsx\nimport { Rows }  from 'storybook-cartesian/react'\n\ncartesian(storiesOf('Button/Cartesian/Tiles', module))\n  .add(() =\u003e ({\n      colors: [{ bg: '#FF5630', fg: '#FFBDAD' }, { bg: '#4C9AFF', fg: '#B3D4FF' }],\n      text: ['Click Me', '', '你好']\n    }),\n    props =\u003e \u003cButton style={{ padding: '1em 3em', border: 'none', backgroundColor: props.colors.bg, color: props.colors.fg }}\u003e{props.text}\u003c/Button\u003e,\n    { \n      renderTitle: titles.renderPropNames(),\n      apply: (stories, candidates) =\u003e {\n        stories.add('all variants', () =\u003e \u003cRows items={candidates}/\u003e)\n      }\n    }\n  )\n```\n\n\n### Premade title renderers\n\nYou can pick a title renderer from a premade collection:\n\n* `renderCheckSignIfExists` - renders the prop name and a 'check' sign if it exists, 'x' if missing\n* `renderPropNames` - renders just the prop names given\n* `renderProps` - render a `prop=value` format\n\n\nYou can use one of these like so:\n\n```js\nimport cartesian, { titles } from 'cartesian'\ncartesian(storiesOf('Button/Cartesian'))\n    .add(\n        seedfn,\n        componentRender,\n        { renderTitle: titles.renderCheckSignsIfExists() }\n    )\n```\n\nWhich produces the following title with `props = { oneProp: null, twoProp: 2}`:\n\n`x oneProp | ✓ twoProp`\n\nThere are more renderers that you can explore (also - happy to get PRs with more!).\n\n### Beautiful names for variants\n\nIf you'd like prop values to have logical names, try `renderWithLegend`:\n\n```js\nimport cartesian, { renderWithLegend } from 'cartesian'\n\nconst complex = { foo:1, bar: 2 }\ncomplex.toString = () =\u003e 'complex-1'\n\nconst titleWithLegend = renderWithLegend({\n  '#FF5630': 'primary',\n  '#FFBDAD': 'secondary',\n  '#4C9AFF': 'primary-opt',\n  '#B3D4FF': 'secondary-opt',\n  'Click Me': 'english',\n  [complex]: 'complex object',\n  '': 'empty',\n  '你好': 'chinese'\n})\n\ncartesian(storiesOf('Button/Cartesian (legend)', module))\n  .add(() =\u003e ({\n    colors: [{ bg: '#FF5630', fg: '#FFBDAD' }, { bg: '#4C9AFF', fg: '#B3D4FF' }],\n    text: ['Click Me', '', '你好']\n  }),\n    props =\u003e \u003cButton style={{ padding: '1em 3em', border: 'none', backgroundColor: props.colors.bg, color: props.colors.fg }}\u003e{props.text}\u003c/Button\u003e,\n    {\n        renderTitle: titleWithLegend(props =\u003e `\"${props.text}\" ${props.colors.bg + '/' + props.colors.fg}`),\n    }\n  )\n```\n\n`renderWithLegend` takes a legend dict, that maps actual prop values to the ones you give in the legend. \n\nThen, it takes a normal renderTitle function that you supply, and it will make sure prop values will be legend values.\n\nIf you want just a top level legend translation (not going into all values in a data structure) use `renderWithLegendFlat`.\n\n### Validating Variants\n\nSome times, not all prop combinations make sense. For example if you have an `isLoading` and a `results` props it doesn't make\nsense to have both `true` and `results` populated:\n\n```javascript\n// doesn't make sense\n\u003cSearchResults isLoading={true} results={['hello', 'world']}\u003e\n```\n\nFor this, we have an `valid` function that we can add, and the following will filter out this invalid combination:\n\n```javascript\ncartesian(storiesOf('Button/Cartesian'))\n    .add(\n        seedfn,\n        componentRender,\n        { \n            renderTitle,\n            valid: props =\u003e !(props.isLoading \u0026\u0026 props.results)\n        }\n    )\n```\n\nSome 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. \n\n\n![](media/variants.png)\n\n\nFor this, we have another optional function:\n\n\n```javascript\nconst allVariantsInOne = (stories, variants)=\u003e{\n    const story = variants.map(c=\u003e(\n        \u003cdiv\u003e\n            \u003cdiv\u003e{c.title}\u003c/div\u003e\n            \u003cdiv\u003e{c.story}\u003c/div\u003e\n        \u003c/div\u003e))\n    stories.add('all variants', ()=\u003e story)\n}\n\ncartesian(storiesOf('Button/Cartesian'))\n    .add(\n        seedfn,\n        componentRender,\n        { apply: allVariantsInOne }\n    )\n```\n\n## Contributing\n\nFork, implement, add tests, pull request, get my everlasting thanks and a respectable place here :).\n\n### Thanks\n\nTo all [Contributors](https://github.com/jondot/storybook-cartesian/graphs/contributors) - you make this happen, thanks!\n\n## Copyright\n\nCopyright (c) 2018 [Dotan Nahum](http://gplus.to/dotan) [@jondot](http://twitter.com/jondot). See [LICENSE](LICENSE.txt) for further details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjondot%2Fstorybook-cartesian","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjondot%2Fstorybook-cartesian","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjondot%2Fstorybook-cartesian/lists"}