addon-docs: Descriptions per Story #8527
donaldpipowitch posted onGitHub
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:
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.
@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. π <img width="519" alt="Screen Shot 2020-01-08 at 11 39 20 AM" src="https://user-images.githubusercontent.com/952681/72005803-a60b2a00-320b-11ea-8690-f76103580246.png">
@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
- 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?
- I had a look at existing loaders - afaik it's only
@storybook/source-loader
, right? Any reason why it lives in thelib/
directory even though it is specific to the "the storysource and docs addons". Should this new loader live inlib/
as well? - 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. Thesource-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 insidesrc/
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 thesource-loader
and rename it tostory-loader
? It could potentially be less confusing and easier to extend for uses cases like adding descriptions per story. - 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 istest: [/\.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-usemodule.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 π
- I don't have a strong preference here. For
react-docgen
we use a babel plugin and forvue-docgen-api
we use a webpack loader. Forsource-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! - Yeah
source-loader
is a library that's shared between two addon, so it's inlib
π. - Yeah that sounds pretty reasonable to me. @libetl @atanasster any thoughts about generalizing to
story-loader
? - Currently the
addon-docs
andaddon-storysource
presets registersource-loader
. I think that you shouldn't have to "pay the price" forsource-loader
unless you're using an addon that uses it. But I think that configuring it using the user'smain.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 . 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 fallback: 'iteration'
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:
- 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. - 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. - 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: <img width="585" alt="Screenshot 2020-06-17 at 13 17 34" src="https://user-images.githubusercontent.com/22667137/84892230-7b1be080-b09d-11ea-9a0f-4ac58ef68fc8.png">
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.
import dedent from 'ts-dedent';
...
{ parameters: { docs: { description: { component: dedent` import {Icons} from './list';
<Icons />
`
}
}
}
} ...
```
The same problem: <img width="1085" alt="Π‘Π½ΠΈΠΌΠΎΠΊ ΡΠΊΡΠ°Π½Π° 2020-11-12 Π² 1 04 07" src="https://user-images.githubusercontent.com/803674/98869681-385f0880-2483-11eb-8733-51d2150f6c10.png">
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={{ ... }} />
@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 />
</>
),
is it possible to pass some mdx to the component description ?
I tried this:
import notes from './notes.mdx';
export default {
title: 'CustomDescription'
parameters: {
docs: {
description: {
component: notes
}
},
}
};
but got the error:
`markdown-to-jsx: the first argument must be a string`
@kylemh has funded $100.00 to this issue.
- Submit pull request via IssueHunt to receive this reward.
- Want to contribute? Chip in to this issue via IssueHunt.
- Checkout the IssueHunt Issue Explorer to see more funded issues.
- Need help from developers? Add your repository on IssueHunt to raise funds.
@trouba:
is it possible to pass some mdx to the component description ?
Yes, it's possible easily using raw-loader
:
import notes from '!raw-loader!./notes.mdx'
export default {
...
parameters: {
docs: {
description: {
component: notes
}
},
}
}
notes
is now a variable pointing to the raw contents (string) of notes.mdx
file
Ofc you can also add the parameters
per-story export also:
// imagine a stories file with multiple stories such as this:
export const MyStory = (args) => <MyComponent {...args}/>
MyStory.storyName = 'My cool story';
MyStory.parameters = {
docs: {
storyDescription: notes // sadly accepts only String and not a function (component)
},
}
Functions can be passed as values to parameters.docs.page
:
export default {
...
parameters: {
docs: {
page: () => (
<>
<Title>My Story</Title>
<Description markdown={docs}/>
Whatever else..
</>
),
},
},
}
But not for a specific story's description parameters.docs.description
:
MyStory.parameters = {
docs: {
description: {
// "story" property should also accepts a function, just like "page" for default export
story: () => (
<>
lots of complex things goes here
</>
),
},
},
}
It would be great for story
property to accept String or Function as values, for more control over story-specific descriptions
It's unclear from this thread whether I should use docs.description.story
or docs.storyDescription
but neither one rendered anything. π€·ββοΈ
@shilman I figured it out. I ran into the other bug (called a feature in another thread), where the first story doesn't render its description. π€·ββοΈ This is pretty counter-intuitive to expectations, but I was able to circumvent it by rendering a custom DocsPage layout.
OlΓ©!! I just released https://github.com/storybookjs/storybook/releases/tag/v7.0.0-alpha.47 containing PR #19684 that references this issue. Upgrade today to the @next
NPM tag to try it out!
npx sb upgrade --prerelease
Closing this issue. Please re-open if you think there's still more to do.
Hey @shilman would you like the bounty to go to you, SB, or to @izhan who I saw you reference in the PR?
@kylemh it 100% should not go to me, @shilman did the actual work here :)
thank you @shilman for adding this feature!
alrighty! just claim it by clicking the badge on OP @shilman
@shilman has rewarded $90.00 to @shilman. See it on IssueHunt
- :moneybag: Total deposit: $100.00
- :tada: Repository reward(0%): $0.00
- :wrench: Service fee(10%): $10.00
@izhan next time i'm in SF, dinner's on me -- thank you for paving the way on this one!!! β€οΈ
@kylemh thanks so much for funding this -- if you make it to Taiwan on your world travels, dinner's on me here too β€οΈ
Sorry to reply to this closed issue, I'm on 7.alpha.50 and use comment like this:
// Some comment about this
export const LoggedOut = Template.bind({});
LoggedOut.args = {};
But it doesn't get generated, am I missing some setup?
@shilman I figured it out. I ran into the other bug (called a feature in another thread), where the first story doesn't render its description. π€·ββοΈ This is pretty counter-intuitive to expectations, but I was able to circumvent it by rendering a custom DocsPage layout.
@matthew-dean can you point me to that thread? I'd also be interested in your workaround. This is a pretty frustrating feature that is making it challenging to convey examples in the way I need.
Sorry to reply to this closed issue, I'm on 7.alpha.50 and use comment like this:
export const LoggedOut = Template.bind({});
LoggedOut.args = {};
But it doesn't get generated, am I missing some setup?
I am also running into the same issue and all links to examples mentioned above are broken. Have you been able to find a fix?
I am on Storybook 7.0.20