Closed michael-ecb closed 9 months ago
No, you need to use the API for dynamic stories AFAIK
ok thanks!
@shilman perhaps you have an example to give us? without it we can't upgrade to the latest version ;/
import { storiesOf } from '@storybook/react';
const cases = ['a', 'b', 'c'];
const stories = storiesOf('foo', module);
cases.forEach(label => stories.add(label, () => <>{label}</>));
@shilman The storiesof API document that you linked states that it's a legacy API. What is the current way of adding multiple stories programatically? I could not find anything in the csf docs at https://storybook.js.org/docs/react/api/csf
@matyasf we don't have one. what's your use case?
It's very important for us that CSF be statically analyzable, so we're not planning on adding a dynamic story API. One common use case I've seen is combinatorial testing, and we might support that through some other mechanism, such as this proposal.
We are making a UI library and generating component variations programatically. These are fed to Chromatic which compares them to the last result, this way we can see easily and precisely what visual changes a code change caused.
Heres out stories.js: https://github.com/instructure/instructure-ui/pull/399/files#diff-55e315ae08c20edbe6c750231bdfa74a92bdc0752777f9332db54675e17af835
and the variation generator: https://github.com/instructure/instructure-ui/tree/master/packages/ui-component-examples
Same use case as @matyasf here
Thanks @matyasf. I believe the combos proposal--possibly with some extra features--should satisfy your use case. We don't plan to formally deprecate storiesOf
until we have a replacement that we feel confident about, so you shouldn't need to worry about the "legacy" thing at this time. We just want everybody who doesn't need this feature on CSF, because it will save a lot of pain later on.
This might be another example of the benefit of an api.
We have a ~200 emails that we would like to storybook + chromatic with.
They all follow the same pattern so could easily be generated by finding all files that end in email.tsx
and looping over them.
Any suggestions are welcome :D
@IanEdington Thanks for sharing that! The "CSF-compatible" way to do this would be to write a custom loader for those email files, which should be as simple as the for-loop you describe. We'll create a sample when we deprecate storiesOf
to make it easy for people with this use case to transition off.
cc @jonniebigodes
the combos proposal
@shilman I've read the doc about this proposal, is there an issue to follow implementation? (I couldn't find it)
I wonder if it could also be used for color palettes/shades, to present design tokens.
It's just a proposal, we haven't agreed on anything and haven't started implementing it. However, we are intent on deprecating storiesOf
and will try to get a suitable alternative in place before we pull the trigger.
@shilman ok, thanks. I've not used storiesOf
yet, but I find ColorPalette
/ColorItem
, Typeset
, etc. not enough for presenting design tokens (I get them in a JSON file generated with Style Dictionary), so I'm looking for alternatives.
@nhoizey look into MDX -- you can embed arbitrary react elements in there to do whatever you want
@shilman I'm already using MDX, I explained it in another issue, let's continue there: https://github.com/storybookjs/storybook/issues/7671#issuecomment-797064663
I've come up with an idea that worked for me quite good when trying to "generate" stories dynamically for an icons
folder.
Hope this workaround could help someone. It does not exactly generate stories, but creates your input selector dynamic, so everytime you add a new icon into your icons
folder and export it on your index.js
the storybook will be updated
The basic folder structure is as follow:
icons > ArrowRight.js
index.js
file at your root folder (icons)
1.1 In this file export all your icons as followed: export { default as ArrowRight} from './ArrowRight'
On your Icons.stories.js
2.1 Import all icons: import * as icons from './index'
2.2 Create a story where it's possible to select your icons dynamically (need to install - withKnobs)
e.g.
export const IconDefaultPicker = () => {
const componentOptions = select('component', Object.keys(icons), 'Cat', 'main')
const fontSizeOptions = select(
'fontSize',
['default', 'inherit', 'large', 'medium', 'small'],
'large',
'main',
)
const htmlColorOptions = select(
'htmlColor',
['inherit', 'primary', 'secondary', 'action', 'disabled', 'error'],
'primary',
'main',
)
return (
<SvgIcon
component={icons[componentOptions]}
fontSize={fontSizeOptions}
htmlColor={htmlColorOptions}
/>
)
}
Just wanted to note I have a use case similar to @IanEdington: we have some components that display some complex data, and we pass that to the component as a plain JS object. These are generated as JSON by a separate system, and we have some JSON "expected" test files for that system to ensure we don't regress anything. We would like to generate a separate story for each JSON file. I can do that with storiesOf now. I skimmed over the proposal linked above, and I think that would also work for our use case (mostly as a degenerate case of a lot of variations of one complex prop).
Hello, I think the previous approach using knobs was better as it allowed to easily create dynamic stories.
For example with knobs it is easy to do something like
import React from 'react';
import {storiesOf} from '@storybook/react';
import {componentFactory} from "../factories/componentFactory";
import {text, object} from '@storybook/addon-knobs';
const testConfig = [
{
"type": "DefaultCreateButton",
"storyName": "Contained",
"props": {
"resource": "users"
},
"config": {
"label": "Test",
"variant": "contained"
}
},
{
"type": "DefaultCreateButton",
"storyName": "Text",
"props": {
"resource": "users"
},
"config": {
![Screen Shot 2021-10-09 at 5 02 46 PM](https://user-images.githubusercontent.com/25964088/136676849-6c733ca8-742d-4fb4-be66-739113a8ee47.png)
"label": "Test",
"variant": "text"
}
},
{
"type": "CreateResourceWithRelated",
"storyName": "Primary",
"props": {
'resource': "carrier_bids",
'resourceName': "Carrier Bids",
'relatedResourceName': "Items",
'relationshipField': "carrier_bid_items",
'fields': [
{
source: "id",
"label": "ID"
}
],
'record': {
id: 1,
owner_username: 'test_user',
'carrier_bid_items': []
},
},
"config": {
'resource': 'carrier_bids',
'fieldProps': [
{
label: "Charge Type",
field: "charge_type",
defaultValue: "Line Haul",
size: "small",
variant: "filled"
},
{
label: "Amount",
field: "amount",
defaultValue: 0,
type: "number",
size: "small",
variant: "outlined"
},
{
label: "Currency",
field: "currency",
defaultValue: "USD",
size: "small",
variant: "outlined"
}
]
}
}
]
for (let conf of testConfig) {
const compType = conf.type
const comp = componentFactory({'type': compType})
storiesOf(compType, module)
.add(conf.storyName, (args) => {
let _args = {}
let _config = {}
const configGroupId = 'Config';
const propsGroupId = 'Props';
for (const [k, v] of Object.entries(conf.config)) {
if (typeof v === 'object') {
_config[k] = (object(k, v, configGroupId))
continue
}
_config[k] = (text(k, v, configGroupId))
}
for (const [k, v] of Object.entries(conf.props)) {
if (typeof v === 'object') {
_args[k] = (object(k, v, propsGroupId))
continue
}
_args[k] = (text(k, v, propsGroupId))
}
return comp(React, _config)(_args)
})
}
where my components are all defined like this
import {CreateButton} from "react-admin"
const DefaultCreateButton = (React, buttonConfig) => {
return (props) => {
console.log(props, buttonConfig)
const to_resource = props.resource
return (
<CreateButton style={{backgroundColor: props.backgroundColor}}
basePath={to_resource}
label={buttonConfig.label}
variant={buttonConfig.variant}/>
)
}
}
export default DefaultCreateButton;
My components are factories, so we can define some initial config and then allow parent components to still pass props.
Is it possible to do this using the new Controls approach ???
It seems impossible due to inability to do dynamic export in js.
We are also in need of this feature. We have a set of json objects, each having the args for the component being rendered in the story. These json objects are automatically generated by a different tool by analyzing a git hub repo. We want to start storybook with stories for each of them and go through manually and write automated tests to verify that component rendered fine for each of these cases.
We have a similar use case. We have a bunch of themes that are passed to a white-labelled app and our storybook needs to be able to show a story for each component named for the theme so a user can see how a component looks with each theme. The themes get added to regularly. We want to be able to maintain a single themes.ts file and export each theme, then import * as themes from "themes.ts"
, after which, being able to dynamically generate a story per-theme, something like:
const stories = storiesOf('ComponentName', module);
Object.entries(themes).forEach(([themeName, themeData]) => {
stories.add(themeName, (args) => {<ComponentName {...args}>{children}</ComponentName>}, {
chakra: {
theme: themeData
}
});
});
The issue here is that we lose all the nice auto-generated actions, and the args are not handled like the new version of storybook (I only just worked out how to do the above so maybe I can still have auto-generated actions and I'm just missing something obvious?).
I can of course export
each themed version manually using the CSF way, and that works perfectly with actions included etc, however then we don't have the ability to see a new theme - we'd have to maintain each story file and add an import for each theme every time we add a new one... that will get very time consuming!
@Irrelon Thanks for sharing that example. It's a great use case and, unlike some of we could probably support it in a way that's statically analyzable (details TBD)
To add another example - we are also using a loop to create stories, which unfortunately conflicts with the goal of making stories statically analyzable. I've been thinking of ways to migrate to CSF and can't think of any way to make the switch without writing a lot of repetitive code.
Our application is effectively a stateless widget that takes a complex state object as a prop. We have hundreds of fixtures with more added weekly to demonstrate how the application works with various versions of this object. We use Chromatic to render a thousand stories, which is great for visual regression testing and feature documentation.
It's very nice to be able to manage these fixtures as standard TS and JSON, as anyone (not just people familiar with StoryBook) can add data quickly.
It's also nice to be able to create all of our stories in one place with the following pseudocode:
import App from './App'
import fixtures from './fixtures'
const stories = storiesOf(`Application`, module);
Object.entries(fixtures).forEach(([name, fixture]) => stories.add(name, () => <App state={fixture} />))
We actually have a loop around this block, so we're calling storiesOf
dynamically as well to add some additional structure to our StoryBook.
@stabback that's another one that we can probably statically analyze, depending on what kind of restrictions we put on the data structure, e.g. "must be an object, where keys are stories and values are the props passed into your component". Needs more thought when we actually go about tackling this.
For discussion's sake, would CSF3 solve your problem?
import App from './App'
export default { title: 'Application', component: App }
export const Key1 = { args: { state: Val1 } }
export const Key2 = { args: { state: Val2 } }
// ...etc.
Where KeyX/ValX
are the existing keys in your JSON file. The key difference is that the fixture file is JS/TS, not JSON, but in terms of human readabilty/code, I'd think this would be on par with your existing approach. What do you think?
Hello everyone, and thanks a lot to the SB team for their hard work. Just want to share what we did to solve this issue with current possible storybook formats.
In order too avoid having tons of configuration in every repository, we are converging with 1 common configuration for all of our repositories, with some allowed customisation.
I have this issue too, I need to create stories dynamically without storiesOf
.
In some repos, we need the buildStoriesJson
option. It doesn't seem to be compatible with storiesOf
. And in other repos, we have cases described in json files, and each one contains a json schema passed as props for a distinct story. We want it to stay dynamic, as when we add a json, it will add automatically a new story.
What I ended up doing is writing a non-ES module to achieve it:
const { getAllStories } = require('./json');
module.exports = {
// NOTICE the require.context to get all the json files in the folder, each json will represent a story
...getAllStories('concepts', require.context(`./json/concepts`, true, /\.json$/)),
default: {
title: 'JSON Schema/Core concepts',
},
};
The getAllStories() function will create an object
{
[StoryNameInCamelCase]: () => <StoryComponent/>
}
This object is then exported via module.exports
.
This is equivalent to the ES format but with dynamic exported content and works perfectly.
export const StoryNameInCamelCase = () => <StoryComponent />;
@jsomsanith-tlnd unfortunately your solution won't work with buildStoriesJson
or storyStoreV7
(the on-demand story store that will become the default in 7.0). We'll still support legacy mode until we can resolve this issue to our satisfaction (hopefully by 8.0?), but i would't count on this working in the future. i'm hopeful we can come up with a workable alternative to support your "json file" use case.
can you please share your getAllStories
function? the link doesn't work 🙏
Ok, good to know. Yeah I hope we can find a solution for that. A working link : getAllStories()
@shilman - Sorry, I missed your response to my comment on February 10th.
While we definitely could just change all of our source files from JSON and JS to CSF, we'd like to avoid that. We also use these files for other testing purposes outside of Storybook. While we could also update our test files to understand how to work with these files in a new format, that is a relationship we'd prefer not to maintain and does seem a bit backward.
Storybook is certainly a first-class citizen in our toolchain and our development cycle, so we definitely could tailor our other tools around its requirements, but it'd be super cool not to have to do that and let data just be data.
@stabback totally understood. i think we'll be able to support the "JSON file" use case before we get rid of storiesOf.
@shilman am I right that there is no working solution to dynamically render stories based some object from JSON if we are opting in to storyStoreV7?
We have json files that are in CSS in JS format. We are hoping to loop over the classes and render thier usage dynamically. IE Typography classes. We can for dynamically build one big template and stuff it into one story, butt hat is not ideal
Correct @sir-captainmorgan21. "butt hat" indeed 😂
The tentative plan is:
storyStoreV7
the default in V7storiesOf
storiesOf
storiesOf
in 8.0Hi @shilman , thanks for this extra info!
- potentially provide some statically-analyzable solutions for two use cases in 7.x
Can I ask, what might these solutions look like? Specifically, would 'combinatorial testing' allow us to loop an object adding stories based on its properties?
We recently added the ability to dynamically add stories by looping an enum using the storiesOf
api and we're keen to keep this ability in future versions
@shilman haha!
Thanks for that update!
@oliverlloyd here's an example of how we might provide combinatorial testing in CSF: https://www.notion.so/chromatic-ui/Storybook-Combos-a5abecd87e9c4e0b86277244af093aea -- not the final spec, just an idea for now
Adding 2 cents to this. Implemented Storybook in 2 orgs where I've loaded test case data from Google Sheets in so colleagues can see how each row of data appears in a feature mounted in Storybook. Very useful solution for Product Managers to be able to browse their features in states they can maintain in Google Sheets, so for loop wrapping storiesOf has been a good solution.
More recently I've done something sort of along the same lines as what @shilman is proposing with Combos, building an intermediate wrapper around args that shunts the values dynamically into Storybook controls, but not ideal - Combos looks like a great solutions to storiesOf.
I think the combos addon is a great idea. However, I don't think it will work well for our use case, which I'd like to share here.
TLDR: We need a way to show extra stories in Chromatic but not in regular storybook.
We have 3 different themes. On storybook, only 1 theme is shown at a time and users can toggle between the themes using the theme-switcher addon. On chromatic however, we show all 3 themes on the same page at the same time. There exists an edge case where having all 3 iterations on one page at the same time will break things, so we need to render a separate story per iteration, 3 in total. We only want this to happen on chromatic, and on normal storybook we would just have the one story instead of 3.
The combos addon wouldn't work because it would put everything on the same story/page, which would break things. storiesOf
won't work because we want to use the new v7 store.
@oriooctopus I'd propose the following workaround for your case:
https://www.notion.so/chromatic-ui/Story-tags-586c7d9ff24f4792bc42a7d840b1142b
For those special case stories that only show up in chromatic, you'd tag those stories with chromatic
and configure them to not be visible in Storybook by default, but to be visible in Chromatic. The exact mechanism for this is still TBD.
What do you think?
@shilman - is there a way to create nested titles for this example? https://github.com/storybookjs/storybook/issues/9828#issuecomment-585480129
Is this the best issue to follow for updates on storiesOf
-type use cases in v7? The v7.0 beta announcement mentions project view 7.0 Burndown, is there an issue on there that’s about dynamic stories generation?
A few words about my use case for this if it helps – roughly what’s described as "JSON fixture" in discussion #18480:
require.context
or just loads of imports) to load those fixtures and then use storiesOf
to generate stories.Here’s an example script if this helps.
I believe this specific use case might still be possible with a Babel transform or macro generating CSF, but we’ve moved away from Babel for everything else (using instead vanilla TypeScript or ESBuild or SWC). In an ideal world we’d even be able to generate stories at runtime in the browser – make an API request to our pattern library generator, and generate stories based on that.
@thibaudcolas we're not going to fix this for 7.0. the workaround is to use the following flag in .storybook/main.js
:
module.exports = {
features: {
storyStoreV7: false
}
}
This will use the old, unoptimized way of loading Storybook. So you'll miss out on a lot of the performance optimizations in V7, but you'll still be able to use the storiesOf
API. We will address this properly in a 7.x release and plan to remove storiesOf
support entirely in 8.0 once we have a solution that we consider acceptable for common use cases described in this issue.
For @carbon/charts (monorepo), there are five packages: Angular, Vanilla JavaScript, React, Svelte/Sveltekit, and Vue.js. For each environment, there are 179 stories created for the charts using storiesOf() and a big array. There's another 28 for diagrams (applicable for Angular and React) plus another 12 docs. Net/net - over 1000 stories.
Here's a link to the Vanilla JS storybook: https://charts.carbondesignsystem.com/?path=/story/docs--welcome. The Welcome page has the links to the others. This was all created in Storybook 5.x.
To move to CSF3, I'm guessing we would need to write our own storiesOf API that would output a file per story into a cache folder that would be viewed as a story source in our main.ts. The files would have to be gitignored and we'd need to clean them before each storybook build.
I'm open to any solution but the capability of building stories from data is very important especially for large publicly-facing monorepos where Storybook is the documentation.
Another item I wanted to add here... we have a number of components that are too simplistic to show as separate stories (like lines, or lines with arrows). For those, we want to follow a bunch of them together in an MDX file for documentation purposes. If we had to turn them into stories, they would look like the Diagrams section here which provides very little value: https://charts.carbondesignsystem.com/angular/?path=/story/diagrams-edges--color
Instead, we want something more like this as an MDX (this example is using storiesOf())... https://charts.carbondesignsystem.com/angular/?path=/story/diagrams--start-here but without navigation nodes on the left for each component. Essentially we want stories to appear in the MDX but not on their own.
OK, I finally have a proposal for deprecating StoriesOf and providing some rough paths forward for users. I've documented this in an RFC, which even includes a working prototype for people to play with: https://github.com/storybookjs/storybook/discussions/23177. Feedback is welcome on the RFC discussion or in DM on Discord if you prefer chatting about it http://discord.gg/storybook
Please use the documented experimental indexer API to generate stories dynamically. Closing this issue. Let us know if the indexer API isn't sufficient for your use case.
Hi,
I have a folder with many icon components and I need to load them dynamically into stories. is it possible with CSF like it was possible before : in this previous issue
thanks