Would be nice to be able to have "Substories" or a Hierarchy of stories. My case involves various mini "apps" being contained in the same repo. A simple solution would be an option to display stores named like ui.core.foo
and ui.core.bar
like:
โโโ core
โโโ bar
โโโ foo
With support for expanding and collapsing nodes.
Currently, we've no plan to implement that. That's because it makes navigation harder. You can namespace story kinds with dot like "ui.core", "ui.app". Then you can filter them as you need.
If there are a lot of stories, you can start a few storybook instances. You can do that by having two storybook config directories. But, anyway that's an extreme case.
I'm willing to concede this point, and thought I'd just make a different config and run it on a different port and whatnot...
But I think it'd be much better to allow storybook to take multiple config files... and then toggle between named config files, perhaps reloading...
As for UI to switch configs, it would only appear if your config file "loaded" other config files, and it could be sidebar items at the top or bottom of the sidebar nav.
Anyway - I think for larger apps, if you can't (or don't) split out configs, it's kinda crazy.
Adding additional configs seems to be overly complex. What about a toggle for classic/hierarchy view? I'm happy spike out an implementation over the next few days.
This would be a very valuable feature to me as well, but for organization of component types within a single app rather than for multiple apps.
I would be more than happy to provide any help in shaping an implementation that could work for both use cases if this is able to move forward.
@travi One of our another idea is to provide a drop down menu just below the filter box to select the category.
A category is assigned in the config.js and different set of files. So, we can have a another layer of grouping.
I think that type of solution would be enough to satisfy my needs at this point. In addition, I think the namespacing convention mentioned above could still be a reasonable way to assign the category that could be interpreted into the choices in the drop down. Such a solution would enable linking across categories to still remain simple as well.
The way that we're getting around this in the app I'm building (hundreds of components, organized inside loose "pillar" areas) is with a script that dynamically writes out the stories for the area we're currently working on.
find.file(
/\.story\.js/,
path.resolve(__dirname, '../src/app/components', targetComponentPath),
function(files) {
var requires = files.map(function(file) {
return "require('" + path.relative(__dirname, file) + "');";
});
fs.writeFileSync(path.resolve(__dirname, '../.storybook/stories.js'), requires.join("\n"));
}
);
This means that Storybook doesn't even have to build the other components. I would love some level of support for this as a built-in option.
any updates on this one?
+1
I'm argee thats a very useful feature!
+1
+1
+1
+1
+1
+1
Hey @arunoda, has there been any progress on the categories implementation front?
If not, does anyone else have an example app that toggles between two storybook configs?
+1 I absolutely need one additional level of nesting :/
+1
+1
+1
+1
+1
+1
well looks like while your app grows, the components list grows too, and you need some more nesting. 1 more level would already cover much cases
+1
Hey, Guys!
Despite the fact that such a feature isn't planned in the near future this doesn't mean that we can't get such behavior via Storybook Addons API
Here is a such addon:
Adds unlimited levels of nesting for (sub)stories
To add one more nesting level just put .chapter(name)
to your stories:
// stories.js:
storiesOf('React App', module)
.chapter('Left panel')
.add('Button 1', fn(1))
.add('Button 2', fn(2))
.chapter('Bottom Panel')
.add('Input 3', fn(3))
.add('Input 4', fn(4))
.endOfChapter()
.chapter('Header Panel')
.add('Input 5', fn(5))
.add('Input 6', fn(6))
.endOfChapter()
.endOfChapter()
.chapter('Right panel')
.add('Button 7', fn(7))
.add('Button 8', fn(8))
.endOfChapter()
Knobs
, addWithInfo
and other addonsstoryDecorator
to wrap all chaptersAny feedback will be very appreciated! :)
@UsulPro Nice!
@UsulPro Storybook Chapters is a brilliant solution. Thanks!
@UsulPro seems to be excactly what I was looking for, thanks!
Hi all! Not trying to compete with @UsulPro (who did an awesome job with storybook-chapters
), but I came up with a small, slightly different solution that allows you to toggle between different groupings of stories with a button in the preview
window. This is useful for us in particular because we want to be able to switch easily between a sort of related components view
a la the bootstrap documentation and a detailed components view
a la what storybook is well-suited for and we have a ton of components to show off. I'd consider this a lightweight version of setting up multiple storybook instances:
If it's useful to you, you can check it out here - https://github.com/majapw/storybook-addon-toggle
I built on @UsulPro's awesome storybook-chapters
to make a storybook loader that will mirror your component file hierarchy as Storybook chapters: storybook-filepath-chapters
With this, I can put my stories in a _stories
file or folder right inline with my components. The loader finds all the story files and maps them into a corresponding navigational structure.
Thanks for the warm feedback, guys!
Really cool to see @hadfieldn's storybook-filepath-chapters
! ๐
I like storybook-addon-toggle
as an example, that it is desirable to have a possibility to build a hierarchy not only in depth but also in the top. Actually, technically it's possible, but I think it's hard to choose the best way (staying within addons API). Perhaps this can be done using decorators (like @majapw) or via addon panels.
I don't plan to add a hierarchy over stories yet, but storybook-chapters
addon now has an API, are able to simplify the construction of such a hierarchy:
enable
/disable
to show/hide your stories
it works this way:
-
add enable()
/disable()
to your stories. As an argument, specify the callback to which the control function will be transferred.
let toLight = () => {};
let toDark = () => {};
storiesOf('Heroes Lightside', module)
.enable((en) => { toLight = en; })
.add('Yoda', info('Yoda'))
.add('Mace Windu', info('Mace Windu'));
storiesOf('Heroes Darkside', module)
.disable((en) => { toDark = en; })
.add('Darth Sidious', info('Darth Sidious'))
.add('Darth Maul', info('Darth Maul'));
then you can use toLight(false)
to hide Heroes Lightside
and toDark(true)
to show Heroes Darkside
stories. You might want to put toLight
and toDark
into some decorators or maybe to callback from other stories. I'll show the simplest possible example:
storiesOf('Choose Your Side', module)
.add('Lightside', () => {
toLight();
toDark(false);
return (<div>{'Lightside selected'}</div>);
})
.add('Darkside', () => {
toDark();
toLight(false);
return (<div>{'Darkside selected'}</div>);
});
So now we have 3 sets of stories: Choose Your Side
, Heroes Lightside
and Heroes Darkside
. Of the last two, only one is visible and the first one allows you to switch.
in the next release I plan to add the ability to control the visibility of the stories via customizable addons panel
-
With enable/disable feature you can build custom navigation with your preferred logic.
We will be implementing a hierarchy browser, but would love concepts how the community thinks it should be done:
UX wise, I like this idea: http://multi-level-push-menu.make.rs/demo/basichtml/basichtml.html
Configuration I don't know yet.. We could use file exploration and mirror the filesystem, or we could do something like this: https://github.com/sm-react/storybook-chapters/issues/1#issue-215446017
@ndelangen have you given thought to (at least optionally?) allowing the navigation to be defined outside the stories? It seems to me that there might be value in treating how the story looks (the preview area / iframe) and how you want to organize the browsing (the manager) as separate concerns.
@jackmccloy I'm interested, can you tell me more about what you mean?
i've mentioned in a different issue, but my target with categories would be to align mostly with atomic design. pattern lab is the official style guide approach for atomic design, but adding categories to storybook would fill the last remaining gap.
i already arrange my components in top level folders for those categories, so being able to load components into categories based on top level folders would be a great thing the shoot for too.
@travi Can you give me a print of your folder layout?
I'm definitely interested at improving storybook for this exact purpose, But I'm interested what would be technically required to read this categorisation from your folder structure.
its essentially
project root
|
+--
| +-- atoms
| | +-- foo
| | +-- index.js // the component
| | +-- stories.js
...
| +-- molecules
| | +-- bar
| | +-- index.js
| | +-- stories.js
...
| +-- organisms
| | +-- baz
| | +-- index.js
| | +-- stories.js
does that help? i have multiple components under each top level folder, sometimes further grouped by another folder level. happy to provide more detail if it would be helpful
ok, so what we could do is set a flag in config.js
. something like autoDiscoverStories
or so. Which would mean you do not have to import stories manually, and the filesystem folders would be used as categories.
@ndelangen I guess what I'm thinking is this: right now, our conversation is around "how do we make the navigation better", but it assumes that there will be a single navigation that everyone will use. I feel that it might be worth talking about ways to make the navigation extensible, in a similar way to how addons extend the functionality of the stories themselves.
One possibility:
Currently, each story is added in two steps - a first step where a category is assigned, and a second step where a title is assigned, i.e.
storiesOf('storyCategory', module).add('storyTitle', () => <Component />)
You can chain adding multiple stories to the same category, but the structure limits flexibility to an extent - all stories must have a category and a title, and categories are a "higher level" than titles.
But if stories could be defined in a slightly different way, i.e.
const storyData = {
category: "category",
title: "storyTitle",
}
stories.add(() => <Component />, storyData)
we could experiment with different navigation options more easily.
The default navigation could stay as it is. It's a sane default, and probably works well enough for most of us. storyData
could even be optional - stories without a category could appear at the top level, and stories without a title could default to the displayName
of the component.
But the community could experiment with different ways of organizing their stories by (a) adding additional metadata fields to stroyData
and/or (b) changing the way the navigation panel renders based on the metadata fields.
Some ideas:
// add an additional level to the hierarchy called subCategory
const stroyData = {
category: "Buttons",
subCategory: "Blue",
title: "BlueButton",
}
stories.add(() => <BlueButton />, storyData)
// add tags to a story that you could then filter by
const stroyData = {
category: "Buttons",
tags: ["button", "homepage"],
title: "HomepageButton",
}
stories.add(() => <HomepageButton />, storyData)
// have a story to appear in multiple categories
const stroyData = {
categories: ["Buttons", "Homepage Elements"],
title: "HomepageButton",
}
stories.add(() => <HomepageButton />, storyData)
Nice! that's quite out of the box, and indeed extensible. I'm going to think about this for a bit. ๐ค
Awesome. Let me know what you decide on - I'll pitch in where I can regardless of what direction you choose to go - big fan of the project
@jackmccloy's proposal is great, thanks for the cool idea!
However, it seems to discourage one strong use-case for Storybooks which is thinking about UI as a series of "visual test cases" and easily defining UI states as individual stories using an add()
call per state.
Registering the story metadata in the add()
call feels like it's adding the category at the wrong level. I'd like to see the same proposal, but using the storiesOf()
function:
storiesOf({
title: Component,
category: "My Category"
}, module)
.add("when empty", () => <List items=[] />)
.add("with items", () => <List items=["one", "two", "three"] />)
.add("etc.", () => <List items={etc} />);
I like the idea of just being able to take the title from the Component.displayName
and all the other ideas about sub-categories or adding a component to multiple categories, I'd just like to preserve the simplicity of adding states.
one thing to keep in mind, regardless of where the category is defined, is that another file should be able to add to the category. if a category could only be defined from a single file, i think it would be very limiting
I agree @travi โ that's why the category just being a string (which I imagine would map to some dictionary key) is appealing.
I am imagining that I might define my categories in one place to prevent typos like so:
// categories.js
export const Layouts = "Layouts";
export const Components = "Components";
export const Styles = "Styles";
// DashboardLayout.story.js
import { Layouts } from "../categories";
import DashboardLayout from "./DashboardLayout";
storiesOf({
title: DashboardLayout,
category: Layouts
}, module)
.add("default", () => <DashboardLayout />);
but that would be an implementation detail left up to my app.
@theinterned @jackmccloy I like your suggestions.
I'm thinking how you might use your suggestions in a hierarchy of arbitrary depth. Perhaps instead of category
/subCategory
it could be path
with an array of path components. (I know you weren't necessarily intending specifics there, just riffing on your ideas.)
I also like the idea of a configuration option to use the filesystem to create the nav hierarchy. With this option enabled, the path
argument would be optional.
This is more of a stretch goal, but it might be good for each page of stories in the hierarchy to be loaded as a separate chunk, to keep the storybook lightweight as it gets larger. It would also be cool to allow the storybook loader to run with a specific filesystem folder as a root context, so that it could build a storybook with only the stories defined in that folder rather than all the stories in the entire project.
What do you people think about defining / registering categories up front in your config?
// config.js
import { configure, addCategory } from '@kadira/storybook';
function init() {
require('../src/stories');
addCategory({
id: 'atom',
name: 'Atoms',
index: 0
});
addCategory({
id: 'molecule',
name: 'Molecules',
index: 1
})
}
configure(init, module);
// component.story.js
import Component from "./Component";
storiesOf({
title: Component,
category: 'atom'
}, module)
.add("default", () => <DashboardLayout />);
We may as well support an array: category: ['atom', 'deprecated']
, why not?
This would help making sure categories are placed in the right order, which in atomic design, is important.
Retrieving categories from config would be nice, magic string are bad ๐
that makes sense to me.
also, +1 for pulling the category for the story from what was defined in the config rather than hoping to match strings
@ndelangen defining categories up front so we can control ordering would be HUGE! But I think it'd be important to make it so that categories _can_ be defined in the config, not that they _must_ be defined there.
One thing I love about Storybook is that I can tell whether or not a component is in Storybook just by checking if whether there's a story.jsx
file co-located with the component. That guarantee -
that if a story.jsx
file exists, a story exists - is an important one that shouldn't be undone by pre-defining categories, imo.
From that view, needing an id
and an index
for categories in the config might not even be necessary - something like this (assuming it uses the options plugin) could work
setOptions({
categoryOrder: [
"First Category",
"Second Category",
"Third Category",
});
where First Category
, Second Category
, and Third Category
are guaranteed to appear first, second, and third, and any other categories declared in stories will appear alphabetically after those three.
This approach could also be a smart way to control arbitrary-depth nesting, too, by doing something like this:
categoryOrder: [
{
"Atoms": [
{
"Buttons": []
}
],
}, {
"Molecules": [],
}],
Stories with the category "Buttons" would appear inside Atoms -> Buttons
. Stories with the category "Atoms" would appear inside Atoms
, below Buttons
(but not inside it), etc.
Users would get one level of depth w/o any config (just like now), and arbitrary levels of depth with minimal config. Importantly, it'd be the categories that have depth (set at the config level) rather than the stories themselves (i.e. stories would only set their category - they wouldn't define where that category appears in the hierarchy).
@theinterned I agree with you re: needing to preserve the simplicity of adding states. I hadn't thought of that, probably b/c I use the knobs addon heavily. So for me, I try to have a 1-1 relationship between components and stories, and my story titles are descriptive of the component rather than descriptive of the state the component is in.
One potential solution that might work for both use cases would be to do something like this
const storyData = {
category: "category",
title: "first item",
}
stories.add(() => <Component />, storyData)
.add(() => <Component />, {title: "second item"})
.add(() => <Component />, {title: "third item"})
where (a) the order of the stories can be controlled from where they're declared (as opposed to needing an external config) and (b) the storyData
param can preserve the previous object, overwriting only the values that are explicitly passed.
Just a thought.
while i would be thrilled with even just top-level categories, if things do go far enough to support nested categories, it is worth noting that it is not safe to assume that category names would be unique between different categories.
continuing the atomic design example, it is common to have a subcategory of the same name across each of the top-level categories of atoms, molecules, and organisms. in the pattern lab demo, forms are a good example of this. individual field elements are listed under atoms, the combination of field and label are listed under molecules, and multiple fields grouped as a complete form are shown under organisms.
An interesting thought would be to consider what if the category could be defined with a callback that gets the title, the story and the path to the the story file ... and also some meta data the user can pass to configure the callback.
storyData = {
title: Component,
category: ({ title, story, storyPath, meta }) => someCategoryPath,
meta: { ..whateverMeta }
}
The only requirement is that the callback needs to return an object defining a category path to the story:
storyData.category() //=> returns the below array
// a simple category path might look like:
[ "One category" ];
// The path for a story nested three categories deep would look like:
[ "Parent Category", "Child Category", "Grandchild category where the story lives" ];
This would allow people to write any category system they want.
If you want to have global config you could register that inside the callback and use the custom meta data to configure which categories / subcategories you want to register the story with.
categories: [
{
"Atoms": [
{
"Buttons": []
}
],
}, {
"Molecules": [],
}];
function setCategory({ meta }) {
const { categroyPath } = meta; // maybe a dot path string like "Atoms.Buttons" ?
const category = categroyPath.split('.'); // [ "Atoms", "Buttons" ]
return validatePath(category, categories); // categories["Atoms"]["Buttons"] is a valid path
}
If you want to set the category structure based on file structure you have the path info to do that with.
function setCategory({ storyPath }) {
// for story path `src/components/Atoms/MyComponent.story.js`
let folders =storyPath.split('/'); // [ "src", "components", "Atoms", "MyComponent.story.js" ];
folders = without(folders, 'src'); // ["components", "Atoms", "MyComponent.story.js" ];
folders.pop(); // [ "components", "Atoms" ]
return folders;
}
If you just want to use a simple category name you can just use one! (And maybe category could take one of either a simple string, an array describing a category path or a callback that returns a category path).
Now the sky's the limit!
on a similar note, for defining sort order I'd suggest a addCategorySort
function you could register that takes the tree category structure generated by loading all the stories and sorts it.
import { configure, addCategorySort } from "@kadira/storybook";
addCategorySort( categories => /* sort logic here */ );
configure(loadStories, module);
@travi I hadn't considered the need for duplicate-named categories, but I agree that it's important. Any thoughts on a solution? This is what pops into mind for me, but maybe there's a better solution:
const storyData = {
categories: ["Buttons"], // any category with the title "buttons"
}
const storyData = {
categories: ["Atoms.Buttons"], // any category with the title "buttons" that also has the parent category "atoms"
}
@theinterned I dig the approach, but worry that it might make things harder / less intuitive for typical users (who need/want something that works well out of the box) for the benefit of the power user (who want something that works perfectly with a bit of effort).
@jackmccloy yeah ... I agree that implementing a function shouldn't be a requirement for everyone. But it seems that there are a number of different use cases that people are looking to support and so a callback system seems to be a nice way to provide extensibility for everyone to customize their own use case.
To assuage the worry that it makes the "happy path" harder, I recommend the following:
storydData.category
accept a string in which case the category would be a top-level category.storydData.category
accept an array where the elements in the array are the path to the category:// given
storydData.category = ["grand parent", "parent", "story category"];
// category tree would look like:
categories = {
"grand parent": {
"parent": {
"story category": /* the story lives here */
}
}
};
Similarly for sorting we can support the default strategy (alpha no doubt) by default and if there's a need we could provide other pre-built sort strategies (sort based on an object shape, sort indexes in the meta data etc ...);
@ndelangen what do you see as the path forward on this? Is anyone working on it? If not, happy to take a crack at it over the weekend
When I get notice work has started by anyone and their solution is viable I remove the PR needed
label. So, this is currently on the roadmap, but no work has yet been done.
If you want to make a start on it, that'd be super welcome!
@jackmccloy may I join and participate in this work as well if you don't mind?
@UsulPro 100%, I'd be excited about that. Planning to start taking a real look at it on Sunday afternoon (NYC time). If you'll be online at the same time, lmk and we can move the convo over to slack. Otherwise I'll post here with some thoughts after I dig in a bit
@jackmccloy @usulpro I'm definitely interested in working on this as well.
@theinterned That would be great! Connecting in slack?
@UsulPro sorry โ bad stomach flu hit my household.
I've got a hack day coming up at work on Friday and I plan to work on this then. Have you had a chance to get started? I'd be happy to synch up on Slack. I'm in the SB channel.
If you just require one level of nesting, React Storybook Addon Chapters might suit your needs.
I've released the first version of @igor-dv 's excellent implementation of story hierarchy and would love to get your feedback on the alpha so we can make it better before releasing to the broader community:
https://gist.github.com/shilman/947a3d1d4cfdf5c3a8bb06d3d4eb84cf
@1i1it @andrubot @arunoda @atnovember @danielbartsch @franzihubrick @hadfieldn @iaanvn @imsnif @isuvorov @jackmccloy @joeruello @johnnyghost @lnmunhoz @majapw @markopavlovic @mystetskyivlad @mzedeler @ndelangen @nirhart @noahprince22 @revolunet @sethkinast @theinterned @thesisb @travi @usulpro @yangshun @zeroasterisk @zvictor
Just a small quirk I noticed with the story hierarchy:
Depending on whether a directory has subdirectories changes the result of clicking a directory.
If there are subdirs the folder will expand, but if it is down at the story level the story will be auto selected.
A user may wish to view the contents of dir without selecting a story inside.
Could be related to this issue in react-treebeard
https://github.com/alexcurtis/react-treebeard/issues/33
Could be worth exploring the PRs there for the storybooks/react-treebeard
repo
In the previous implementation when selecting a kind
its first story was autoselected. So this functionality I wanted to preserve. But maybe with hierarchy it's already looks like a bug.
In the pic Component 5
is not a directory - it's a kind
.
Actually I don't like this behavior as well...
Long story names wrap oddly
Resizing the sidebar to be really small causes the preview pane to overflow into it.
is it possible to combine hierarchy folders with individual stories? I have some stories I want at the top level, otherwise I have a folder with a single item
Currently if you do this
storiesOf('Something', module).add('top story');
storiesOf('Something.Chapter', module).add('substory');
Then it creates 2 entries for 'Something', one with a folder and one with an item
@TheSisb , thanks, will be fixed in the official release.
@psimyn , in the current implementation it's not possible.. but might be changed.. @UsulPro mentioned this in the initial PR too.
IMO this is not a good behavior (and brings more complexity). Comparing it to the every IDE, there are namespaces (dirs/folders/packages) and could be some items in those namespaces (or near by) with the same name..
Anyway, if this is a desired behavior of the community, it should be changed, but I wouldn't like it be stopper for the release =)
This is the exact solution I needed !!! thank you +1
@psimyn Please open a new issue describing the feature? This issue will be closed real soon with the release of 3.2.0
is having multiple level of nesting now possible with the new CSF format ?
@gaurav5430 it's been possible for some time, see our example here:
CSF:
import React from 'react';
import { linkTo } from '@storybook/addon-links';
import { Welcome } from '@storybook/react/demo';
export default {
title: 'Other/Demo/Welcome',
component: Welcome,
};
export const ToStorybook = () => <Welcome showApp={linkTo('Other/Demo/Button')} />;
ToStorybook.storyName = 'to Storybook';
Hey @ndelangen
Thanks i get that from here: https://storybook.js.org/docs/basics/writing-stories/#story-hierarchy
I think what I want is the ability to create sub-folders based on story.name
and not only the default export title
export default {
title: 'Other/Demo/Welcome',
component: Welcome,
};
export const ToStorybook = () => <Welcome showApp={linkTo('Other/Demo/Button')} />;
ToStorybook.story = { name: 'to/Storybook' };
would show up as Other/Demo/Welcome/To/Storybook
I think a workaround for the above issue would be to create multiple story files and export default with the correct hierarchy in each of them.
like in one.stories.js
:
export default {
title: 'Other/Demo/Welcome/One',
component: Welcome,
};
export const ToStorybookOne = () => <Welcome showApp={linkTo('Other/Demo/Button')} />;
and in two.stories.js
export default {
title: 'Other/Demo/Welcome/Two',
component: Welcome,
};
export const ToStorybookTwo = () => <Welcome showApp={linkTo('Other/Demo/Button')} />;
That way, both the stories would show up as expected in the storybook folder structure
@gaurav5430 that's the recommended usage, and not a workaround. ๐
@gaurav5430 that's the recommended usage, and not a workaround. ๐
Yes, I was just hesitant to do that as both these files are for different states of the same component. In my case, the component has 2 main states and multiple sub states based on those 2 main states. Usually I would keep all the states of the component in the same file, but in this case i would need to have a separate file for hierarchy in stories.
Most helpful comment
Hey, Guys!
Despite the fact that such a feature isn't planned in the near future this doesn't mean that we can't get such behavior via Storybook Addons API
Here is a such addon:
Storybook Chapters
Storybook Chapters
Adds unlimited levels of nesting for (sub)stories
To add one more nesting level just put
.chapter(name)
to your stories:Features
Knobs
,addWithInfo
and other addonsstoryDecorator
to wrap all chaptersDemo page
Project
Example
Any feedback will be very appreciated! :)