Storybook: addon-docs: Descriptions per Story

Created on 23 Oct 2019  ·  37Comments  ·  Source: storybookjs/storybook

Is your feature request related to a problem? Please describe.

According to the documentation there is currently one Description section on a DocsPage. This section is filled by the JSDoc comment /** */ used by my specified component. That's awesome. I can place a general description here. (E.g. describe what my component <Button />.)

After that I have more specific stories showcasing different usages of a certain component. (E.g. showcasing <Button mode="primary" /> and <Button mode="secondary" />.)

It would be lovely if I could add a JSDoc comment to my stories so they show up on the DocsPage. E.g. something like that:

import React from 'react';
import { Button } from '../../src/components/button';

export default { title: 'Components|Button', component: Button };

export const All = () => (
  <>
    <Button mode="primary">Click me!</Button>
    {' '}
    <Button mode="secondary">Click me!</Button>
  </>
);

/**
 * Only use me once per page for the preferred user action.
 */
export const Primary = () => <Button mode="primary">Click me!</Button>;

/**
 * You can use me several times on a page for neutral actions.
 */
export const Secondary = () => <Button mode="secondary">Click me!</Button>;

Describe the solution you'd like

Everything could than be rendered like this:

image

Describe alternatives you've considered

I could use MDX files for this, but they currently lack any type checking which I'd like to keep (and I don't really want to separate every example out of the MDX file into their own files, because I don't know if this messes with things like the Source Loader for code examples 🤔). Also it's a little bit easier to migrate to for existing Storybooks instead of rewriting everything to MDX.

Are you able to assist bring the feature to reality?

Probably, I can if I find a little bit of time.

docs feature request todo

Most helpful comment

Hey folks – long-time watcher of this thread here! I ended up creating a trivial Webpack loader a while back that got the job done. I only now had some extra time to put it on Github, which you can find here.

Code

// A `primary` button is used for emphasis.
export const Primary = () => <Button primary>Submit</Button>

/**
 * Use the `loading` prop to indicate progress. Typically use
 * this to provide feedback when triggering asynchronous actions.
 */
export const Loading = () => <Button loading>Loading</Button>

Output

Definitely not perfect and a bit specific to my codebase's current setup, hence some quirks here and there. But figured I'd send it out in case anyone was looking for a starting point.

Also happy to improve on it more, but looks like there's bigger, better plans brewing in this thread. Eager to help there too :)

All 37 comments

@donaldpipowitch Yes, I think this is the ideal way to do story descriptions in docs, but haven't had time to put it together yet. It would be amazing if you could figure it out.

In the meantime, there's a not as nice workaround by adding the story parameter parameters.docs.storyDescription to each story. The docgen would be much nicer though!

@shilman Ah, that is interesting! storyDescription is not documented, right? Thank you for the workaround.

It is now 😉 https://github.com/storybookjs/storybook/pull/8535

The jsdoc solution you describe (and i've also wanted for awhile) is 100x better, though a bit of work to figure out.

Actually as I'm writing this, it occurs to me that it could be a pretty trivial webpack loader that either annotates the export with some __docgenInfo kind of field, or even automatically inserts the storyDescription parameter based on the comment... any interest in making that happen?

I'd probably try to solve https://github.com/storybookjs/storybook/issues/8126 first, before I can tackle this one. But that won't happen within the next two weeks I guess. (Deadline approaching😅)

But after that. Sure, why not. I guess I can also have a look how the JSDoc is currently parsed from the component itself for the main instruction to check how it was done there.

@donaldpipowitch Are you still planning to do this? It would be super awesome because my team would prefer writing stories with default syntax rather than using MDX files. 😁

Probably not within the next two weeks or so, but in general I'm still open for this. But if anyone else wants to tackle this first just go ahead 👍

But shouldn't the workaround work for you as well?

@donaldpipowitch It does, but I much prefer the comment solution. The workaround isn't great for large blocks of text, which I would define like so:

MyStoryFunction.story = {
  parameters: { 
    docs: {
      storyDescription: `
        Imagine this to be a much longer block of text that spans
        several lines.
      `,
    },
  },
};

Unfortunately, using template literals results in the description being rendered as inline code, so it's all monospaced and whatnot. I have resort to using quotes, which I think we all know are lame to use for multiline blocks of text. 😋
Screen Shot 2020-01-08 at 11 39 20 AM

@Smolations you can use dedent or similar to fix that in the short term

@shilman The main problem is that the description is rendered as a code block. If rendered as plain text the indentation issue becomes moot. And speaking of indentation issues, since all of my stories all defined as export function StoryName() { return (...jsx...); }, all of my "Show Code" blocks are missing a single indentation for all of the content in the function definition:

export function StoryName() { 
return (
  // ...jsx (correctly indented)...
); 
}

Just wanted to give you a lil heads up, but I can open a separate issue if you'd like me to formalize the problem. 👍

@Smolations AFAIK the descriptions are rendered as markdown.

There's already an issue for code formatting. If you up vote it that's the least duplicative way of letting us know you think it's important. If it really bothers you, PR's are always welcome in storybook

Small update on this @shilman . Looks like my above storyDescription issue only happens when using _multiline_ template literals defined _inside_ the parameters block itself. So, to summarize:

// renders in a code block; no bueno
MyStoryFunction.story = {
  parameters: { 
    docs: {
      storyDescription: `
        Imagine this to be a much longer block of text that spans
        several lines.
      `,
    },
  },
};
// renders as normal text, as desired
MyStoryFunction.story = {
  parameters: { 
    docs: {
      storyDescription: `Imagine this to be a much longer block of text that spans several lines.`,
    },
  },
};
// renders as normal text, as desired (and my chosen approach for this in the near term)
const myStoryFunctionDescription = `
  Imagine this to be a much longer block of text that spans
  several lines.
`;

MyStoryFunction.story = {
  parameters: { 
    docs: {
      storyDescription: myStoryFunctionDescription,
    },
  },
};

Interesting that there are different results for these approaches, but it is what it is, haha. Now I have less anxiety around this as I eagerly await @donaldpipowitch to tackle this issue's feature request if/when he has time! 😋

@Smolations I think the indentation => code is a markdown thing. I'm guessing it's 3 spaces, which is why the first one is treated as code and third one as text. We use ts-dedent in storybook -- worth checking out.

I'm also really excited about this feature and will tackle this once I work through my own backlog if nobody else gets to this sooner. (wink wink @donaldpipowitch)

Oh that peer pressure 😆 I gave that some thoughts yesterday and have a couple of small questions.

... it could be a pretty trivial webpack loader that ... automatically inserts the storyDescription parameter based on the comment

  1. Is there a reason why you thought about a webpack loader instead of a babel plugin here? Is it because not all files which contain stories are currently processed by babel? If yes - are there plans to change that in the future?
  2. I had a look at existing loaders - _afaik_ it's only @storybook/source-loader, right? Any reason why it lives in the lib/ directory even though it is specific to the "the storysource and docs addons". Should this _new_ loader live in lib/ as well?
  3. I thought about how to name this loader... and thought to myself _"Well, it'll transform files containing stories, so it will probably be the story-loader."_, because I didn't want to make it too specific (e.g. descriptions-per-story-loader 😋) so it can be re-used later for other use cases. And than I thought: _"Wait a minute. The source-loader should also only transform story files!"_ It is _not_ meant to transform the _sources_ of the things I document in Storybook (which we mostly refer to as the _source code_ and which often lives inside src/ directories). Maybe that was one of the reasons which led to my previous MR here: https://github.com/storybookjs/storybook/pull/8773. tl;dr: Would it make sense to generalize the source-loader and rename it to story-loader? It could potentially be less confusing and easier to extend for uses cases like adding descriptions per story.
  4. If we have such a _general loader_ for Storybook (which than also makes more sense to live inside lib/) - do we maybe want to auto-apply it to the webpack configs? I think currently you need manually add this which can also lead to smaller problems. E.g. as mentioned here https://github.com/storybookjs/storybook/blob/078366baefc781ea8abc84de48872ff3d6ce8dda/addons/storysource/README.md#install-using-preset the default to which the loader applies is test: [/\.stories\.jsx?$/],. That didn't matched to _my_ config which led to the misconfiguration in https://github.com/storybookjs/storybook/pull/8773. But we could essentially just re-use module.exports.stories from .storybook/main.js here, because here we already have _one_ place where we configure all of our stories. (I'd not tackle this question to solve this specific issue. It would be an issue on its own and I could create one, if you think it's worthy to track.)

@donaldpipowitch I like the way you think 😅

  1. I don't have a strong preference here. For react-docgen we use a babel plugin and for vue-docgen-api we use a webpack loader. For source-loader it's webpack. I don't fully understand which is better, but webpack seems to be a bit easier to configure in Storybook. Happy to be persuaded otherwise!
  2. Yeah source-loader is a library that's shared between two addon, so it's in lib 😄.
  3. Yeah that sounds pretty reasonable to me. @libetl @atanasster any thoughts about generalizing to story-loader?
  4. Currently the addon-docs and addon-storysource presets register source-loader. I think that you shouldn't have to "pay the price" for source-loader unless you're using an addon that uses it. But I think that configuring it using the user's main.js stories configuration would be wonderful if we can do it cleanly.

@shilman I had a look at the source-loader code. I saw estraverse.traverse is used a lot ~and that you needed to use fallback: 'iteration'~. There were also some helper to get the correct parser (JS/TS/Flow)... To add a description to a story it would probably be way more straight forward to have a babel plugin which can be added to an existing babel config (either completely manually or somehow automatic - maybe in lib/core/src/server/common/babel.js...?.)

The only thing which is maybe _not_ straight forward to do is to limit the transformation to files containing stories. AFAIK there is nothing which all files containing stories really have in common. It would be wonderful to have just some isStoryFile(somePath) API in the core which checks if a certain file contains stories or not. Not sure if this is possible, if we offer more ways than just main.js stories to load stories.

tl;dr:

  1. Maybe source-loader is a bad example to build upon as it needs to access and process the raw source code, so it can be included as source code example. That's why you have to dance around the parsers a little bit.
  2. I'd recommend to create a Babel plugin which needs to be added manually and which needs to have a config equivalent to main.js stories as one of the options it needs.
  3. At a later point in time one could maybe optimize this by automatically adding and automatically configuring this plugin?

Is that fine for you? Than I'd start writing plugin in lib/story-plugin.

If not, I'd probably just write and publish a custom plugin to do that just as a proof of concept 🤔

@donaldpipowitch what are the implications of fallback: 'iteration'?

Implementing this as a babel-plugin is a-okay with me. 💯

As it happens, I'm spending a bunch of with babel these days (e.g. https://github.com/storybookjs/storybook/pull/9838). We're switching over to babel-plugin-react-docgen for all the react prop table generation (js, ts, flow), and it will really simplify that whole area.

I'm not sure how we'll get users to configure it yet, but we'll have to solve that one way or another, and having a second babel plugin gets more brains to the table.

As far as naming, babel-story-description-plugin or babel-story-plugin is more specific. But feel free to keep it broad if you have more designs for extending it.

@donaldpipowitch what are the implications of fallback: 'iteration'?

A never mind. I think I confused estraverse here with a different lib. Sorry about that.

Hey folks – long-time watcher of this thread here! I ended up creating a trivial Webpack loader a while back that got the job done. I only now had some extra time to put it on Github, which you can find here.

Code

// A `primary` button is used for emphasis.
export const Primary = () => <Button primary>Submit</Button>

/**
 * Use the `loading` prop to indicate progress. Typically use
 * this to provide feedback when triggering asynchronous actions.
 */
export const Loading = () => <Button loading>Loading</Button>

Output

Definitely not perfect and a bit specific to my codebase's current setup, hence some quirks here and there. But figured I'd send it out in case anyone was looking for a starting point.

Also happy to improve on it more, but looks like there's bigger, better plans brewing in this thread. Eager to help there too :)

Why did this get closed @shilman?

@IamMille it's open?

I'm subscribing to this because this is a feature I really want too

Hello everyone! Having JSDoc support would be great, but in the meantime I've been using a little utility for adding docs to the stories based on storyDescription mentioned above.

// utils/storybook.js
import set from 'lodash/set';

export function docs(component, description) {
  set(component, 'story.parameters.docs.storyDescription', description);
  return component;
}

Then you can use it to add docs to the story in the following way:

import { docs } from 'utils/storybook';

export const Story = () => (
  <div>
    Bar!
  </div>
);
docs(Story, `
Hello world!

# markdown support working well!

1. list
1. list
`);

Effect:
Screenshot 2020-06-17 at 13 17 34

Small thing, but hope it helps somebody!

It would be fine if we could set something like TheStoryObject.storyDescription = "This is a story description" or, even better, commenting above the story, as @izhan showed ❤️

@jimmyandrade the "manual" syntax as of 6.0 is:

Story.parameters = {
  docs: { description: { story: 'This is a story description' } } }
}

TS does not work. what's left to be done here?
I managed to force it this way:

import { Meta } from '@storybook/react/types-6-0'
import { BaseStory, Annotations } from '@storybook/addons/dist/public_api'
import { StoryFnReactReturnType } from '@storybook/react/dist/client/preview/types'

// ...

type TemplateType = BaseStory<IconProps, StoryFnReactReturnType> &
Annotations<IconProps, StoryFnReactReturnType> &
{ story?: { parameters: { docs: { storyDescription: string }}}}

const Template: TemplateType = args => <Component {...args} />

export const example = Template.bind({})

example.story = {
  parameters: {
    docs: {
      storyDescription: 'element',
    },
  },
}

@Tymek why doesn't docs.description.story work?

My fault! :) It works:

const Template: Story<ButtonProps> = args => <Button {...args} />
export const example = Template.bind({})
example.parameters = {
  docs: { storyDescription: 'test' },
}

I was adding it on Template and I got Property 'docs' does not exist on type ....

Thanks!

Hi there. I stumbled upon the same problem when using

docs: {
   storyDescription: `description`
}

It would be great to be able to write markdown in template strings, but at the same time when you need to use something like escaping to write pieces of code in the markdown.

Are there any advances in the ideas of implementing this feature?

Send many greets to your team!

Is there any way to include images using relative paths?:

import image from './button.png';

{
    parameters: {
        docs: {
            description: {
                component: '<img src={image} />'
            }
        }
    }
}

Did you try this @monolithed

                component: `<img src="${image}" />`

Did you try this @monolithed

                component: `<img src="${image}" />`

I did, It does not work (

I would also like to pay attention to:

1.

import Icons from './list';

...

{
    parameters: {
        docs: {
            description: {
                component: `<Icons />`
            }
        }
    }
}
...
- Warning: The tag <Icons> is unrecognized in this browser. If you meant to render a React component, start its name with an uppercase letter.
    2.
import dedent from 'ts-dedent';

...

{
    parameters: {
        docs: {
            description: {
                component: dedent`
                             import {Icons} from './list';

                              <Icons />
                 `
            }
        }
    }
}
...

The same problem:
Снимок экрана 2020-11-12 в 1 04 07

How to include MD with its components?

The problem I want to solve is the combination of argTypes.controls and MDX at one place. Is it possible?

You can set component parameters in MDX like this:

<Meta title="..." parameters={{ ... }} />

See https://storybook.js.org/docs/react/writing-docs/mdx

@shilman could you provide an example how to use the parameters option? It's hard to understands due to there was another option argTypes.
Why not use the ArgsTable component to parametrize controls?

I found an alternative solution of the first problem:

import {
    Title,
    Subtitle,
    Description,
    Primary,
    ArgsTable,
    Stories,
    PRIMARY_STORY,
} from '@storybook/addon-docs/blocks';

...

    parameters: {
        docs: {
            page: () => (
                <>
                    <Title />
                    <Subtitle />
                    <Description />
                    <Primary />
                    <ArgsTable story={PRIMARY_STORY} />
                    <Stories />
                    <Icons />
                </>
            ),
Was this page helpful?
0 / 5 - 0 ratings

Related issues

arunoda picture arunoda  ·  3Comments

oriSomething picture oriSomething  ·  3Comments

ZigGreen picture ZigGreen  ·  3Comments

zvictor picture zvictor  ·  3Comments

purplecones picture purplecones  ·  3Comments