{
"$type": "site.standard.document",
"content": {
"$type": "site.standard.content.markdown",
"text": "When I was exploring the next version of the design system for [Compass](https://www.compass.com/), I was really interested in how I could keep designers from recreating symbols that we already have represented in code. They’d have to painstakingly make all of the possible states and interactions for every component for the design library. I knew there was a better way so I cobbled a few systems together in order to do it.\n\n## Legacy system\n\nI was an early adopter of [Storybook](https://storybook.js.org/) and leveraged it to visualize the components I was building for the new design system. Writing a story was helpful to create one ideal state, or perhaps wiring it into the knobs addon allows for different configurations to happen with a few button clicks. However, what I really wanted was to generate stories based on the configuration options for each component. I was working on a very early prototype with web components so I didn’t have proptypes to hook into. I opted to create an `component.props.js` file for each component to help generate configurations.\n\nFrom here, I used a package called [`combos`](https://www.npmjs.com/package/combos) to create all the possible permutations of options and then create a new Storybook file to read with those new generated components. This was also used to create the files required for [`html-sketchapp`](https://www.npmjs.com/package/@brainly/html-sketchapp), which could render the HTML into [Sketch](https://www.sketch.com/) symbols.\n\n## Component Story Format\n\nStorybook is no longer focusing on their initial implementation of writing stories, [the `storiesOf()` function](https://github.com/storybookjs/storybook/blob/next/lib/core/docs/storiesOf.md), in favor of the [Component Story Format (CSF)](https://github.com/ComponentDriven/csf). The CSF is described as an open standard for UI component examples based on JavaScript ES6 modules. And the way to write a story seems fairly simple:\n\n```\nexport default { title: 'atoms/Button' };\nexport const text = () => <Button>Hello</Button>;\nexport const emoji = () => <Button>😀😎👍💯</Button>;\n```\n\nThe default export is the metadata about your story; the title, the component, and maybe some additional configuration options. Each named export is a story. So in the above example, you’d have two stories. One named “Text” and one named “Emoji”. And coming soon, [CSF v3](https://storybook.js.org/blog/component-story-format-3-0/) will introduce an even smaller amount of code to create a story:\n\n```\nexport default { component: Button };\nexport const text = { args: { children: 'Hello' } };\nexport const emoji = { args: { children: '😀😎👍💯' } };\n```\n\nHowever, if you know anything about the JavaScript module ecosystem you’ll see the problem with my goal. **There’s no way to generate stories using CSF**. In fact, Storybook doesn’t even treat this file as a *real* module at first. Storybook will actually read this file, create an [abstract syntax tree (AST)](https://en.wikipedia.org/wiki/Abstract_syntax_tree) and then begin parsing the file for information [\\[source\\]](https://github.com/storybookjs/storybook/blob/next/lib/csf-tools/src/CsfFile.ts).\n\nLet’s compare this to the original `storiesOf()` method of writing stories.\n\n```\nstoriesOf('atoms/Button', module)\n .add('text', () => <Button>Hello</Button>)\n .add('emoji', () => <Button>😀😎👍💯</Button>)\n```\n\nYou can see that the original method of writing stories is more flexible and allows for someone to create a collection of stories and begin iterating to add to Storybook. Here’s a cute one-liner to define a collection of stories.\n\n```\nconst stories = {\n text: () => <Button>Hello</Button>,\n emoji: () => <Button>😀😎👍💯</Button>,\n};\nObject.entries(stories).reduce((acc, ([name, fn])) => acc.add(name, fn), storiesOf('Button', module));\n```\n\nInterestingly, it seems that [CSF compiled to `storiesOf()`](https://medium.com/@domyen/storiesof-is-not-deprecated-598c322588c) for Storybook but I’m not sure if that’s still the case.\n\n## If it ain’t broke\n\nWhile Storybook no longer recommends the `storiesOf()` approach because it [conflicts with improvement plans](https://storybook.js.org/blog/storybook-on-demand-architecture/); we can still use it today. Here’s how I’d begin to integrate with a React ecosystem; reading [`PropTypes`](https://www.npmjs.com/package/prop-types) to generate stories.\n\nFirst, we need a way to read proptypes from a component. Luckily, there’s a package for that: [`parse-prop-types`](https://www.npmjs.com/package/parse-prop-types). Here’s how we might begin to use this:\n\n```\nimport parsePropTypes from 'parse-prop-types';\n\nexport default function (component) {\n const proptypes = parsePropTypes(component);\n}\n```\n\nIf you get a collection of types that all return as `'custom'`, you’ll need to include the following before defining proptypes for each component.\n\n```\nimport 'parse-prop-types';\n```\n\nFor more information, like the return object structure, I recommend reading [the project’s `README.md`](https://github.com/diegohaz/parse-prop-types/blob/master/README.md).\n\nNext, we’ll need to prepare a collection of properties and possible values. For `type: 'bool'` and `type: 'oneOf'`, the values are fairly straight forward. We’ll put these in a lookup object for easy access.\n\n```\nconst DEFAULT_TYPE_ASSIGN = {\n 'bool': () => [false, true],\n 'oneOf': ({ meta }) => meta.type.value,\n}\n```\n\nFor the inputs like `number` or `string`, we’ll need to allow custom fixtures to be included. We’ll prepare an `options` argument for our function to supply this.\n\n```\nexport default function (component, options) {\n const { fixtures } = options;\n const proptypes = parsePropTypes(component);\n}\n```\n\nThe `fixtures` option will be an object that has each prop as a key and an array of possible values. Now we can send that into our assignments; choosing the correct fixture prior to running the function. We’ll see how this works later.\n\n```\nconst DEFAULT_TYPE_ASSIGN = {\n 'bool': () => [false, true],\n 'oneOf': ({ meta }) => meta.type.value,\n 'string': ({ fixture }) => fixture,\n 'number': ({ fixture }) => fixture,\n}\n```\n\nYou can continue to add more as needed. I specifically omit the `func` type since it’s not a visual change but you can include it if it’ll help your project.\n\nOk, we’re ready to loop through all of the proptypes.\n\n```\n\nconst DEFAULT_TYPE_ASSIGN = {\n 'bool': (acc, { property } ) => Object.assign(acc, { [property]: [false, true] }),\n 'string': (acc, { property, fixture }) => Object.assign(acc, { [property]: fixture }),\n 'number': (acc, { property, fixture }) => Object.assign(acc, { [property]: fixture }),\n 'oneOf': (acc, { property, meta } ) => Object.assign(acc, { [property]: meta.type.value }),\n}\n\nconst isFn = (fn) => typeof fn === 'function';\n\nconst values = Object.entries(propTypes).reduce((acc, [property, meta]) => {\n const fn = DEFAULT_TYPE_ASSIGN[meta.type.name];\n return isFn(fn) ? fn(acc, { meta, property, fixture: fixtures[property] }) : acc;\n}, {});\n```\n\nNotice, I’ve updated the functions in the lookup to have an accumulator as the first parameter; each will return the accumulator with the values for each property if it exists. Otherwise, it will return the unaltered accumulator.\n\nOk, now we’re ready to create permutations. I’m using a more recent package for this: [`combinate`](https://www.npmjs.com/package/combinate). Let’s start putting everything together.\n\n```\nimport parsePropTypes from 'parse-prop-types';\nimport combinate from 'combinate';\n\nconst DEFAULT_TYPE_ASSIGN = {\n 'bool': (acc, { property } ) => Object.assign(acc, { [property]: [false, true] }),\n 'string': (acc, { property, fixture }) => Object.assign(acc, { [property]: fixture }),\n 'number': (acc, { property, fixture }) => Object.assign(acc, { [property]: fixture }),\n 'oneOf': (acc, { property, meta } ) => Object.assign(acc, { [property]: meta.type.value }),\n}\n\nconst isFn = (fn) => typeof fn === 'function';\n\nexport default function (component, options) {\n const { fixtures } = options || {};\n const propTypes = parsePropTypes(component);\n const values = Object.entries(propTypes).reduce((acc, [property, meta]) => {\n const fn = DEFAULT_TYPE_ASSIGN[meta.type.name];\n return isFn(fn) ? fn(acc, { meta, property, fixture: fixtures[property] }) : acc;\n }, {});\n\n const storyArgs = combinate(values);\n}\n```\n\nThe new `storyArgs` variable holds an array of objects, but we’re not quite done yet. We need to have one large collection with the structure of `{ name: { ...args } }`, where the `name` is a unique identifier for each story. I opted for a simple [`slugify`](https://www.npmjs.com/package/@sindresorhus/slugify) of the `JSON` to create the identifier.\n\n```\nimport slugify from '@sindresorhus/slugify';\n\nconst storyArgs = combinate(values).reduce((acc, combo) => {\n const str = JSON.stringify(combo);\n return Object.assign(acc, { [slugify(str)]: combo });\n}, {});\n```\n\nAt this point you can just return the resulting `storyArgs` and pipe them into the `storiesOf()` method approach that I suggested above:\n\n```\nimport React from 'react';\nimport { default as Button } from './Button';\nimport { storiesOf } from '@storybook/react';\nimport everythingAllAtOnce from '../src'; // Your custom generator function\n\nconst fixtures = { text: ['hello', '😀😎👍💯'] };\nconst stories = everythingAllAtOnce(Button, { fixtures })\nObject.entries(stories).reduce((acc, ([name, args])) => acc.add(name, () => <Button { ...args }/>), storiesOf('Button', module));\n```\n\nOr, if you’re really fancy, you could provide more options to just have the function run the `storiesOf()` method internally.\n\n```\neverythingAllAtOnce(Button, {\n fixtures,\n storiesOf, // Used internally to begin defining the stories produced\n callback: (args) => <Button { ...args }/>, // How each story should render with the given args\n});\n```\n\nNot showing the code to implement here, you can use your imagination.\n\n## Cool, what should we do with this?\n\nFor starters, this would a good way to begin generating assets for your component library. The [story.to.design](https://story.to.design/) tool by [‹div›RIOTS](https://divriots.com/) can transform stories into [Figma](https://www.figma.com/) components. This’ll help keep the Figma library up to date with the code because it’s pulling from the code. Imagine, writing minimal code to generate all of these assets!\n\nIt’s also a good way to determine configurations that you may not have expected. Perhaps you notice with review of the permutations, icons in inline buttons don’t work well, so you might want to set a conditional in the component to either warn or omit the icons.\n\nSpeaking of checking configurations, this would be great for testing. You could ensure a check in every configuration for some baseline metrics and even assert that all configurations that use the same props will render in the same expected way. This is truly a full coverage scenario without writing much code.\n\n## Full circle\n\nThis brings me back to the introduction of the CSF format; which doesn’t support this type of behavior at all. There’s [a document](https://www.notion.so/Storybook-Combos-a5abecd87e9c4e0b86277244af093aea) outlining the possibility of “Storybook Combos”, which is only a proposal with no affirmitive direction yet.\n\nI hope that whatever the future holds for Storybook that the team sees the benefits of `storiesOf()` and attempts to support the ability for dynamic stories in the future. I’m sure the design system community has more use-cases for dynamic stories that are past just the needs outlined above.",
"version": "1.0"
},
"coverImage": {
"$type": "blob",
"ref": {
"$link": "bafkreihapznupkio6sqez34hpzww3jyiguy5siqnmvwh7yikyga2vdrcsa"
},
"mimeType": "image/png",
"size": 321140
},
"description": "A Storybook approach to visualize all my components' configurations and why its not recommended in Storybook today.",
"path": "/posts/dynamic-storybook",
"publishedAt": "2022-06-14T00:00:00.000Z",
"site": "https://blog.damato.design",
"tags": [
"storybook",
"components"
],
"textContent": "When I was exploring the next version of the design system for Compass, I was really interested in how I could keep designers from recreating symbols that we already have represented in code. They’d have to painstakingly make all of the possible states and interactions for every component for the design library. I knew there was a better way so I cobbled a few systems together in order to do it.\n\nLegacy system\n\nI was an early adopter of Storybook and leveraged it to visualize the components I was building for the new design system. Writing a story was helpful to create one ideal state, or perhaps wiring it into the knobs addon allows for different configurations to happen with a few button clicks. However, what I really wanted was to generate stories based on the configuration options for each component. I was working on a very early prototype with web components so I didn’t have proptypes to hook into. I opted to create an file for each component to help generate configurations.\n\nFrom here, I used a package called [](https://www.npmjs.com/package/combos) to create all the possible permutations of options and then create a new Storybook file to read with those new generated components. This was also used to create the files required for [](https://www.npmjs.com/package/@brainly/html-sketchapp), which could render the HTML into Sketch symbols.\n\nComponent Story Format\n\nStorybook is no longer focusing on their initial implementation of writing stories, the function, in favor of the Component Story Format (CSF). The CSF is described as an open standard for UI component examples based on JavaScript ES6 modules. And the way to write a story seems fairly simple:\n\nThe default export is the metadata about your story; the title, the component, and maybe some additional configuration options. Each named export is a story. So in the above example, you’d have two stories. One named “Text” and one named “Emoji”. And coming soon, CSF v3 will introduce an even smaller amount of code to create a story:\n\nHowever, if you know anything about the JavaScript module ecosystem you’ll see the problem with my goal. There’s no way to generate stories using CSF. In fact, Storybook doesn’t even treat this file as a real module at first. Storybook will actually read this file, create an abstract syntax tree (AST) and then begin parsing the file for information [\\[source\\]](https://github.com/storybookjs/storybook/blob/next/lib/csf-tools/src/CsfFile.ts).\n\nLet’s compare this to the original method of writing stories.\n\nYou can see that the original method of writing stories is more flexible and allows for someone to create a collection of stories and begin iterating to add to Storybook. Here’s a cute one-liner to define a collection of stories.\n\nInterestingly, it seems that CSF compiled to for Storybook but I’m not sure if that’s still the case.\n\nIf it ain’t broke\n\nWhile Storybook no longer recommends the approach because it conflicts with improvement plans; we can still use it today. Here’s how I’d begin to integrate with a React ecosystem; reading [](https://www.npmjs.com/package/prop-types) to generate stories.\n\nFirst, we need a way to read proptypes from a component. Luckily, there’s a package for that: [](https://www.npmjs.com/package/parse-prop-types). Here’s how we might begin to use this:\n\nIf you get a collection of types that all return as , you’ll need to include the following before defining proptypes for each component.\n\nFor more information, like the return object structure, I recommend reading the project’s .\n\nNext, we’ll need to prepare a collection of properties and possible values. For and , the values are fairly straight forward. We’ll put these in a lookup object for easy access.\n\nFor the inputs like or , we’ll need to allow custom fixtures to be included. We’ll prepare an argument for our function to supply this.\n\nThe option will be an object that has each prop as a key and an array of possible values. Now we can send that into our assignments; choosing the correct fixture prior to running the function. We’ll see how this works later.\n\nYou can continue to add more as needed. I specifically omit the type since it’s not a visual change but you can include it if it’ll help your project.\n\nOk, we’re ready to loop through all of the proptypes.\n\nNotice, I’ve updated the functions in the lookup to have an accumulator as the first parameter; each will return the accumulator with the values for each property if it exists. Otherwise, it will return the unaltered accumulator.\n\nOk, now we’re ready to create permutations. I’m using a more recent package for this: [](https://www.npmjs.com/package/combinate). Let’s start putting everything together.\n\nThe new variable holds an array of objects, but we’re not quite done yet. We need to have one large collection with the structure of , where the is a unique identifier for each story. I opted for a simple [](https://www.npmjs.com/package/@sindresorhus/slugify) of the to create the identifier.\n\nAt this point you can just return the resulting and pipe them into the method approach that I suggested above:\n\nOr, if you’re really fancy, you could provide more options to just have the function run the method internally.\n\nNot showing the code to implement here, you can use your imagination.\n\nCool, what should we do with this?\n\nFor starters, this would a good way to begin generating assets for your component library. The story.to.design tool by ‹div›RIOTS can transform stories into Figma components. This’ll help keep the Figma library up to date with the code because it’s pulling from the code. Imagine, writing minimal code to generate all of these assets!\n\nIt’s also a good way to determine configurations that you may not have expected. Perhaps you notice with review of the permutations, icons in inline buttons don’t work well, so you might want to set a conditional in the component to either warn or omit the icons.\n\nSpeaking of checking configurations, this would be great for testing. You could ensure a check in every configuration for some baseline metrics and even assert that all configurations that use the same props will render in the same expected way. This is truly a full coverage scenario without writing much code.\n\nFull circle\n\nThis brings me back to the introduction of the CSF format; which doesn’t support this type of behavior at all. There’s a document outlining the possibility of “Storybook Combos”, which is only a proposal with no affirmitive direction yet.\n\nI hope that whatever the future holds for Storybook that the team sees the benefits of and attempts to support the ability for dynamic stories in the future. I’m sure the design system community has more use-cases for dynamic stories that are past just the needs outlined above.",
"title": "Dynamic Storybook",
"updatedAt": "2026-06-13T19:14:17.118Z"
}