storybookjs / storybook

Storybook is the industry standard workshop for building, documenting, and testing UI components in isolation
https://storybook.js.org
MIT License
84.27k stars 9.27k forks source link

Substories/Hierarchy #151

Closed joeruello closed 7 years ago

joeruello commented 8 years ago

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:

ui
└── core
    ├── bar
    └── foo

With support for expanding and collapsing nodes.

arunoda commented 8 years ago

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.

zeroasterisk commented 8 years ago

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.

joeruello commented 8 years ago

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.

travi commented 8 years ago

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.

arunoda commented 8 years ago

@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.

travi commented 8 years ago

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.

sethkinast commented 8 years ago

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.

zvictor commented 8 years ago

201 could help on this.

markopavlovic commented 8 years ago

any updates on this one?

AndruBot commented 7 years ago

+1

AtNovember commented 7 years ago

I'm argee thats a very useful feature!

danielbartsch commented 7 years ago

+1

lnmunhoz commented 7 years ago

+1

isuvorov commented 7 years ago

+1

noahprince22 commented 7 years ago

+1

mystetskyivlad commented 7 years ago

+1

FranziAndFinn commented 7 years ago

+1

majapw commented 7 years ago

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?

TheSisb commented 7 years ago

+1 I absolutely need one additional level of nesting :/

johnnyghost commented 7 years ago

+1

iaanvn commented 7 years ago

+1

mzedeler commented 7 years ago

+1

hadfieldn commented 7 years ago

+1

imsnif commented 7 years ago

+1

1i1it commented 7 years ago

+1

revolunet commented 7 years ago

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

nirhart commented 7 years ago

+1

usulpro commented 7 years ago

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 ![preview](https://raw.githubusercontent.com/sm-react/storybook-chapters/master/doc/img/preview.gif) To add one more nesting level just put `.chapter(name)` to your stories: ```js // 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() ``` ## Features - The hierarchical structure of Substories - Compatible with `Knobs`, `addWithInfo` and other addons - Use `storyDecorator` to wrap all chapters [Demo page](https://sm-react.github.io/storybook-chapters) [Project](https://github.com/sm-react/storybook-chapters) [Example](https://github.com/sm-react/storybook-chapters#example)

Any feedback will be very appreciated! :)

danielbartsch commented 7 years ago

@UsulPro Nice!

hadfieldn commented 7 years ago

@UsulPro Storybook Chapters is a brilliant solution. Thanks!

FranziAndFinn commented 7 years ago

@UsulPro seems to be excactly what I was looking for, thanks!

majapw commented 7 years ago

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

hadfieldn commented 7 years ago

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.

usulpro commented 7 years ago

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. ```js 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: ```js storiesOf('Choose Your Side', module) .add('Lightside', () => { toLight(); toDark(false); return (
{'Lightside selected'}
); }) .add('Darkside', () => { toDark(); toLight(false); return (
{'Darkside selected'}
); }); ``` 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](https://github.com/sm-react/storybook-chapters/subscription) 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.

complete example here

ndelangen commented 7 years ago

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

jackmccloy commented 7 years ago

@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.

ndelangen commented 7 years ago

@jackmccloy I'm interested, can you tell me more about what you mean?

travi commented 7 years ago

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.

ndelangen commented 7 years ago

@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.

travi commented 7 years ago

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

ndelangen commented 7 years ago

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.

jackmccloy commented 7 years ago

@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)
ndelangen commented 7 years ago

Nice! that's quite out of the box, and indeed extensible. I'm going to think about this for a bit. 🤔

jackmccloy commented 7 years ago

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

theinterned commented 7 years ago

@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.

travi commented 7 years ago

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

theinterned commented 7 years ago

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.

hadfieldn commented 7 years ago

@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.

ndelangen commented 7 years ago

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 👎

travi commented 7 years ago

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