-
-
Notifications
You must be signed in to change notification settings - Fork 9.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Document module / storiesof / mdx story formats #7498
Merged
Merged
Changes from 2 commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,160 +3,189 @@ id: 'writing-stories' | |
title: 'Writing Stories' | ||
--- | ||
|
||
Storybook is all about writing stories. A story usually contains a single state of one component, almost like a visual test case. | ||
A Storybook is a collection of stories. Each story represents a single visual state of a component. | ||
|
||
> Technically, a story is a function that returns something that can be rendered to screen. | ||
|
||
A Storybook can be comprised of many stories for many components. | ||
## Basic story | ||
|
||
## Location for Stories | ||
Here is a simple example of stories for a `Button` component: | ||
|
||
There are no rules for this, but in general, stories are easier to maintain when they are located closer to components. | ||
```js | ||
import React from 'react'; | ||
import { action } from '@storybook/addon-actions'; | ||
import Button from './Button'; | ||
|
||
Some examples: | ||
export default { title: 'Button' }; | ||
|
||
<details> | ||
<summary>stories inside component directory</summary> | ||
export const withText = () => <Button onClick={action('clicked')}>Hello Button</Button>; | ||
|
||
export const withEmoji = () => ( | ||
<Button onClick={action('clicked')}> | ||
<span role="img" aria-label="so cool"> | ||
😀 😎 👍 💯 | ||
</span> | ||
</Button> | ||
); | ||
``` | ||
|
||
```plaintext | ||
• | ||
└── src | ||
└── components | ||
└── button | ||
├── button.js | ||
└── button.stories.js | ||
``` | ||
This is what you'll see in Storybook: | ||
|
||
</details> | ||
![Basic stories](../static/basic-stories.png) | ||
|
||
The named exports define the Button's stories, and the `default` export defines metadata that applies to the group. In this case, the title determines the title of the group in Storybook's left-hand navigation panel. | ||
|
||
This example is written in Storybook's [Module format](../../formats/module-story-format/). Storybook also supports: | ||
|
||
- a classic [storiesOf format](../../formats/storiesof-story-format/), which adds stories through Storybook's API. | ||
- an experimental [MDX format](../../formats/mdx-story-format/), which mixes longform Markdown docs and JSX stories. | ||
|
||
Since Module format is a new additon to Storybook, most Storybook examples you'll find in the wild are written in the legacy [storiesOf format](../stories-of-format/). | ||
|
||
Furthermore, Storybook for React Native currently only supports the `storiesOf` format. React Native will get Module and MDX support in a future release. | ||
|
||
## Story file location | ||
|
||
Stories are easier to maintain when they are located alongside the components they are documented. We recommend: | ||
|
||
```plaintext | ||
• | ||
└── src | ||
└── components | ||
└── button | ||
├── button.js | ||
└── button.stories.js | ||
``` | ||
|
||
It's up to you to find a naming/placing scheme that works for your project/team. Other naming conventions: | ||
|
||
<details> | ||
<summary>stories sub-directory in component directory</summary> | ||
|
||
```plaintext | ||
• | ||
└── src | ||
└── components | ||
└── button | ||
├── button.js | ||
└── stories | ||
└── button.stories.js | ||
``` | ||
```plaintext | ||
• | ||
└── src | ||
└── components | ||
└── button | ||
├── button.js | ||
└── stories | ||
└── button.stories.js | ||
``` | ||
|
||
</details> | ||
|
||
<details> | ||
<summary>stories directory outside src directory</summary> | ||
|
||
```plaintext | ||
• | ||
├── src | ||
│ └── components | ||
│ └── button.js | ||
└── stories | ||
└── button.stories.js | ||
``` | ||
```plaintext | ||
• | ||
├── src | ||
│ └── components | ||
│ └── button.js | ||
└── stories | ||
└── button.stories.js | ||
``` | ||
|
||
</details> | ||
|
||
It's up to you to find a naming/placing scheme that works for your project/team. | ||
## Loading stories | ||
|
||
## Writing Stories | ||
Stories are loaded in the `.storybook/config.js` file. | ||
|
||
Here is an example of a basic story: | ||
(Let's assume there's a component called "Button" in `src/components/Button.js`.) | ||
The most convenient way to load stories is by filename. For example, if you stories files are located in the `src/components` directory, you can use the following snippet. | ||
|
||
```js | ||
// file: src/stories/index.js | ||
import { load } from '@storybook/react'; | ||
|
||
import React from 'react'; | ||
import { storiesOf } from '@storybook/react'; | ||
import { action } from '@storybook/addon-actions'; | ||
import Button from '../components/Button'; | ||
|
||
storiesOf('Button', module) | ||
.add('with text', () => <Button onClick={action('clicked')}>Hello Button</Button>) | ||
.add('with some emoji', () => ( | ||
<Button onClick={action('clicked')}> | ||
<span role="img" aria-label="so cool"> | ||
😀 😎 👍 💯 | ||
</span> | ||
</Button> | ||
)); | ||
load(require.context('../src/components', true, /\.stories\.js$/), module); | ||
``` | ||
|
||
This will add stories in the storybook like this: | ||
The `load` function may be called multiple times to load stories from different locations. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Will have to change this :) |
||
|
||
![Basic stories](../static/basic-stories.png) | ||
Storybook uses Webpack's [require.context](https://webpack.js.org/guides/dependency-management/#require-context) to load modules dynamically. Take a look at the relevant Webpack [docs](https://webpack.js.org/guides/dependency-management/#require-context) to learn more about how to use `require.context`. | ||
|
||
This uses Storybook's basic API for writing stories. There are official and third party Storybook [addons](/addons/introduction) for more advanced functionality. | ||
The `load` function is available since Storybook 5.2 and is the recommended way to load stories. It replaces the [configure function](../storiesof-format#legacy-loading), which is still in use in most Storybook examples, and is the only way to currently load stories in React Native. | ||
|
||
## Loading stories dynamically | ||
## Decorators | ||
|
||
Sometimes, stories need to be loaded dynamically rather than explicitly in the Storybook config file. | ||
A decorator is a way to wrap a story with a common set of components, for example if you want to wrap a story in some formatting, or provide some context to the story. | ||
|
||
For example, the stories for an app may all be inside the `src/components` directory with the `.stories.js` extension. It is easier to load all the stories automatically like this inside the `.storybook/config.js` file: | ||
Decorators can be applied globally, at the component level, or individually at the story level. Global decorators are typically applied in the Storybook config files, and component/story decorators are applied in the story file. | ||
|
||
```js | ||
import { configure } from '@storybook/react'; | ||
Here is an example of a global decorator which centers every story in the storybook: | ||
|
||
const req = require.context('../src/components', true, /\.stories\.js$/); | ||
```jsx | ||
import React from 'react'; | ||
import { load, addDecorator } from '@storybook/react'; | ||
|
||
function loadStories() { | ||
req.keys().forEach(filename => req(filename)); | ||
} | ||
addDecorator(storyFn => <div style={{ textAlign: 'center' }}>{storyFn()}</div>); | ||
|
||
configure(loadStories, module); | ||
load(require.context('../src/components', true, /\.stories\.js$/), module); | ||
``` | ||
|
||
Storybook uses Webpack's [require.context](https://webpack.js.org/guides/dependency-management/#require-context) to load modules dynamically. Take a look at the relevant Webpack [docs](https://webpack.js.org/guides/dependency-management/#require-context) to learn more about how to use `require.context`. | ||
|
||
The **React Native** packager resolves all the imports at build-time, so it's not possible to load modules dynamically. There is a third party loader [react-native-storybook-loader](https://github.com/elderfo/react-native-storybook-loader) to automatically generate the import statements for all stories. | ||
|
||
## Using Decorators | ||
|
||
A decorator is a way to wrap a story with a common set of components. Here is an example for centering all components: | ||
And here's an example of component/local decorators. The component decorator wraps all the stories in a yellow frame, and the story director wraps a single story in an additional red frame. | ||
|
||
```jsx | ||
import React from 'react'; | ||
import { storiesOf } from '@storybook/react'; | ||
import MyComponent from '../my_component'; | ||
import MyComponent from './MyComponent'; | ||
|
||
export default { | ||
title: 'MyComponent', | ||
decorators: [storyFn => <div style={{ backgroundColor: 'yellow' }}>{storyFn()}</div>], | ||
}; | ||
|
||
storiesOf('MyComponent', module) | ||
.addDecorator(storyFn => <div style={{ textAlign: 'center' }}>{storyFn()}</div>) | ||
.add('without props', () => <MyComponent />) | ||
.add('with some props', () => <MyComponent text="The Comp" />); | ||
export const normal = () => <MyComponent />; | ||
export const special = () => <MyComponent text="The Boss" />; | ||
special.story = { | ||
decorators: [storyFn => <div style={{ border: '5px solid red' }}>{storyFn()}</div>], | ||
}; | ||
``` | ||
|
||
This only applies the decorator to the current set of stories. (In this example, the decorator is added only to the **MyComponent** story group.) | ||
Decorators are not just for story formatting, they are generally useful for any kind of context needed by a story. | ||
|
||
It is possible to apply a decorator **globally** to all the stories. Here is an example of the Storybook config file: | ||
- Theming libraries require a theme to be passed in through context. Rather than redefining this in every story, just add a decorator. | ||
- Likewise, state management libraries like Redux provide a global data store through context. | ||
- Finally, Storybook [addons](../../addons/introduction) heavily use decorators. For example, the Storybook's [Knobs addon](https://github.com/storybookjs/storybook/tree/next/addons/knobs) uses decorators to modify the input properties of the story based on a UI. | ||
|
||
```jsx | ||
import React from 'react'; | ||
import { configure, addDecorator } from '@storybook/react'; | ||
## Parameters | ||
|
||
addDecorator(storyFn => <div style={{ textAlign: 'center' }}>{storyFn()}</div>); | ||
Parameters are custom metadata for a story. Like decorators, they can also be hierarchically applied: globally, at the component level, or locally at the story level. | ||
|
||
Here's an example where we are annotating our stories with [Markdown](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet) notes using parameters, to be displayed in the [Notes addon](https://github.com/storybookjs/storybook/tree/next/addons/notes). | ||
|
||
configure(function() { | ||
// ... | ||
}, module); | ||
We first apply some notes globally in the Storybook config. | ||
|
||
```js | ||
import { load, addParameters } from '@storybook/react'; | ||
import defaultNotes from './instructions.md'; | ||
addParameters({ notes: defaultNotes }); | ||
``` | ||
|
||
## Using Markdown | ||
This would make sense if, for example, `instructions.md` contained instructions on how to document your components when there is no documentation available. | ||
|
||
As of storybook 3.3, [Markdown](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet) can be used in Storybook by default. Users can import a markdown file which extracts the raw markdown content into a string. The string can then be used in any addon that supports markdown such as notes and info. | ||
Then for componenents that did have documentation, we might override it at the component/story level: | ||
|
||
```jsx | ||
import React from 'react'; | ||
import { storiesOf } from '@storybook/react'; | ||
import MyComponent from './MyComponent'; | ||
import someMarkdownText from './someMarkdownText.md'; | ||
|
||
storiesOf('Component', module).add('With Markdown', () => <MyComponent />, { | ||
notes: { markdown: someMarkdownText }, | ||
}); | ||
import componentNotes from './notes.md'; | ||
import specialNotes from '/.special.md'; | ||
|
||
export default { | ||
title: 'MyComponent', | ||
parameters: { notes: componentNotes }, | ||
}; | ||
|
||
export const small = () => <MyComponent text="small" />; | ||
export const medium = () => <MyComponent text="medium" />; | ||
export const special = () => <MyComponent text="The Boss" />; | ||
special.story = { | ||
notes: specialNotes, | ||
}; | ||
``` | ||
|
||
In this example, the `small` and `medium` stories get the compoonent notes documented in `notes.md` (as opposed to the generic instructions in `instructions.md`). The `special` story gets some special notes. | ||
|
||
## Searching | ||
|
||
By default, search results will show up based on the file name of your stories. As of storybook 5, you can extend this with `notes` to have certain stories show up when the search input contains matches. For example, if you built a `Callout` component that you want to be found by searching for `popover` or `tooltip` as well, you could use `notes` like this: | ||
|
@@ -173,49 +202,36 @@ By default, search results will show up based on the file name of your stories. | |
) | ||
``` | ||
|
||
## Nesting stories | ||
## Story hierarchy | ||
|
||
Stories can be organized in a nested structure using "/" as a separator: | ||
Stories can be organized in a nested structure using "/" as a separator, and can be given a top-level heading using a "|" root separator. | ||
|
||
```jsx | ||
// file: src/stories/index.js | ||
For example the following snippets nest the `Button` and `Checkbox` components within the `Atoms` group, under a top-level heading called `Design System`. | ||
|
||
```jsx | ||
// Button.stories.js | ||
import React from 'react'; | ||
import { storiesOf } from '@storybook/react'; | ||
import Button from '../components/Button'; | ||
import Button from './Button'; | ||
|
||
storiesOf('My App/Buttons/Simple', module).add('with text', () => ( | ||
<Button onClick={action('clicked')}>Hello Button</Button> | ||
)); | ||
|
||
storiesOf('My App/Buttons/Emoji', module).add('with some emoji', () => ( | ||
<Button onClick={action('clicked')}> | ||
<span role="img" aria-label="so cool"> | ||
😀 😎 👍 💯 | ||
</span> | ||
</Button> | ||
)); | ||
export default { | ||
title: 'Design System|Atoms/Button', | ||
}; | ||
export const normal = () => <Button onClick={action('clicked')}>Hello Button</Button>; | ||
``` | ||
|
||
## Organising stories with titles | ||
|
||
Stories can be organized under a title using "|" as a separator: | ||
|
||
```jsx | ||
// Checkbox.stories.js | ||
import React from 'react'; | ||
import { storiesOf } from '@storybook/react'; | ||
import Button from '../components/Button'; | ||
|
||
/** | ||
* The Button stories will show up underneath the 'Components' title. | ||
*/ | ||
storiesOf('Components|Button', module).add('base', () => ( | ||
<Button onClick={() => console.log('Clicked')}>Example Button</Button> | ||
)); | ||
import Checkbox from './Checkbox'; | ||
|
||
export default { | ||
title: 'Design System|Atoms/Checkgox', | ||
}; | ||
export const empty = () => <Checkbox label="empty" />; | ||
export const checked = () => <Checkbox label="checked" checked />; | ||
``` | ||
|
||
If you would prefer to use another character as the separator then you can | ||
configure it using the `hierarchyRootSeparator` config option. Visit the | ||
If you prefer other characters as separators, you can configure this using the `hierarchySeparator` and `hierarchyRootSeparator` config options. See the | ||
[configuration options parameter](/configurations/options-parameter) page to learn more. | ||
|
||
## Generating nesting path based on \_\_dirname | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
--- | ||
id: 'mdx-story-format' | ||
title: 'MDX Story Format' | ||
--- | ||
|
||
[MDX story](https://mdxjs.com/) documentation coming soon! | ||
|
||
For more information, see the [Storybook Docs Technical Preview](https://docs.google.com/document/d/1un6YX7xDKEKl5-MVb-egnOYN8dynb5Hf7mq0hipk8JE/edit?usp=sharing) |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.