mirror of
https://github.com/storybookjs/storybook.git
synced 2025-03-18 05:02:24 +08:00
initial commit for api migration for 6 0
This commit is contained in:
parent
c4d119443a
commit
baf44de180
269
docs/api/addons.md
Normal file
269
docs/api/addons.md
Normal file
@ -0,0 +1,269 @@
|
||||
---
|
||||
title: 'Addons'
|
||||
---
|
||||
|
||||
Addons extend Storybook with features and integrations that are not built into the core. Most Storybook features are implemented as addons. For instance: documentation, accessibility testing, interactive controls, and design previews.
|
||||
Storybook’s addon API makes it easy for you to configure and customize Storybook in new ways. There are countless addons made by the community that unlock time-saving workflows. What addons can do:
|
||||
|
||||
- Add a panel to Storybook (like Action Logger).
|
||||
- Add a tool to Storybook’s toolbar (like zoom or grid).
|
||||
- Add a tab to Storybook (like SB Docs).
|
||||
- Interact and communicate with other addons or Storybook UI
|
||||
- Change Storybook’s state using its APIs.
|
||||
- Navigate within Storybook.
|
||||
- Register keyboard shortcuts (coming soon).
|
||||
|
||||
Browse the [Addon gallery](/addons) to install an existing addon or as inspiration for your own addon. Read on to learn how to make an addon yourself.
|
||||
|
||||
### Storybook basics
|
||||
|
||||
Before writing your first addon, let’s take a look at the basics of Storybook’s architecture. While Storybook presents a unified user interface, under the hood it’s divided down the middle into Manager and Preview.
|
||||
The Manager is the UI where Storybook’s search, navigation, toolbars, and addons are rendered. The Preview area is an iframe where stories are rendered.
|
||||
|
||||
<div style="background-color:#F8FAFC">
|
||||
TODO: get image to match documentation
|
||||
</div>
|
||||
|
||||
Because Manager and Preview run in separate iframes, they communicate across a communication channel. For example, when you select a story within the Manager, an event is sent across the channel, and the selected story is rendered inside the Preview.
|
||||
|
||||
Many of the addon APIs you’ll read about below are abstractions to help make this communication transparent.
|
||||
|
||||
### Getting started
|
||||
|
||||
Let’s write a simple addon for Storybook which:
|
||||
|
||||
- Adds a new “My Addon” panel
|
||||
- Retrieves a custom “myAddon” parameter from stories
|
||||
- Displays the parameter data in the panel
|
||||
|
||||
#### Add story parameters
|
||||
|
||||
Let’s start by writing a story for our addon that exposes a custom parameter. The idea is that our addon will show this parameter in the addon panel.
|
||||
|
||||
```js
|
||||
|
||||
import React from 'react';
|
||||
import Button from './Button';
|
||||
export default {
|
||||
title: 'Button',
|
||||
parameters: {
|
||||
myAddon: {
|
||||
data: 'this data is passed to the addon',
|
||||
},
|
||||
},
|
||||
};
|
||||
export const Basic = () => <Button>hello</Button>;
|
||||
```
|
||||
|
||||
Because we added the story at the component level, the `myAddon` parameter is associated with all stories defined in the file.
|
||||
|
||||
#### Add a panel
|
||||
|
||||
Now let’s add a panel to Storybook in a file called `register.js`, which is the entry point for addons to register themselves.
|
||||
|
||||
```js
|
||||
// register.js
|
||||
import React from 'react';
|
||||
import { addons, types } from '@storybook/addons';
|
||||
import { AddonPanel } from '@storybook/components';
|
||||
|
||||
const ADDON_ID = 'myaddon';
|
||||
const PANEL_ID = `${ADDON_ID}/panel`;
|
||||
|
||||
const MyPanel = () => <div>MyAddon</div>;
|
||||
addons.register(ADDON_ID, api => {
|
||||
addons.add(PANEL_ID, {
|
||||
type: types.PANEL,
|
||||
title: ‘My Addon’,
|
||||
render: ({ active, key }) => (
|
||||
<AddonPanel active={active} key={key}>
|
||||
<MyPanel />
|
||||
</AddonPanel>
|
||||
)
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
This is boilerplate code for any addon that adds a panel to Storybook, and there’s really not much going on here. In this case, we’re just adding a static div that renders when the panel is selected in Storybook’s UI.
|
||||
|
||||
#### Display story parameter
|
||||
|
||||
Next, let’s replace the `MyPanel` component from above to show the parameter.
|
||||
|
||||
```js
|
||||
// register.js
|
||||
import { useParameter } from '@storybook/api';
|
||||
const PARAM_KEY = 'myAddon';
|
||||
const MyPanel = () => {
|
||||
const value = useParameter(PARAM_KEY, null);
|
||||
const item = value ? value.data : "";
|
||||
return <div>{item}</div>;
|
||||
}
|
||||
```
|
||||
|
||||
The new version is made smarter by `useParameter`, which is a [React hook](https://reactjs.org/docs/hooks-intro.html) that updates the parameter value and re-renders the panel every time the story changes.
|
||||
|
||||
Storybook’s addons API provides hooks like this so all of that communication can happen behind the scenes and you can focus on your addon functionality.
|
||||
|
||||
#### Register the addon
|
||||
|
||||
Finally, let’s hook it all up. Addons are typically published as standalone packages, but they can also be written locally in an existing Storybook project. We’ll make our addon a local addon.
|
||||
In a Storybook project, update your [`.storybook/main.js`](../configure/overview#configure-story-rendering):
|
||||
|
||||
```js
|
||||
// .storybook/main.js
|
||||
module.exports = {
|
||||
addons: ['path/to/register.js']
|
||||
}
|
||||
```
|
||||
The path can be an absolute location on your file system, or a path relative to your `.storybook` directory (e.g. `./my-addon/register.js` if you defined the addon inside your `.storybook` folder).
|
||||
|
||||
If you get an error similar to:
|
||||
|
||||
```sh
|
||||
ModuleParseError: Module parse failed: Unexpected token (92:22)
|
||||
|
||||
You may need an appropriate loader to handle this file type.
|
||||
var value = this.state.value;
|
||||
var active = this.props.active;
|
||||
return active ? <div>{value}</div> : null;
|
||||
}
|
||||
}]);
|
||||
```
|
||||
It is likely because you do not have a `.babelrc` file or do not have it configured with the correct presets:
|
||||
|
||||
```json
|
||||
{
|
||||
"presets": ["@babel/preset-env", "@babel/preset-react"]
|
||||
}
|
||||
```
|
||||
|
||||
Now restart/rebuild storybook and your addon should appear in the addons panel. Furthermore, as you navigate between stories, the parameter displayed should update accordingly.
|
||||
|
||||
|
||||
#### Next steps
|
||||
|
||||
In the previous example, we introduced the structure of an addon, but barely scratched the surface of what addons can do.
|
||||
|
||||
To dive deeper we recommend [Learn Storybook’s “creating addons”](https://www.learnstorybook.com/intro-to-storybook/react/en/creating-addons/) tutorial. It’s an excellent walkthrough that covers the same ground as the above introduction, but goes further and leads you through the full process of creating a realistic addon.
|
||||
|
||||
### Addon recipes
|
||||
|
||||
Once you understand the basics of writing an addons, there are a variety of common enhancements to make your addon better.
|
||||
|
||||
#### Disabling the addon panel
|
||||
|
||||
It’s possible to disable the addon panel for a particular story.
|
||||
|
||||
To make that possible, you need to pass the `paramKey` element when you register the panel:
|
||||
|
||||
```js
|
||||
addons.register(ADDON_ID, () => {
|
||||
addons.add(PANEL_ID, {
|
||||
type: types.PANEL,
|
||||
title: 'My Addon',
|
||||
render: () => <div>Addon tab content</div>,
|
||||
paramKey: 'myAddon', // this element
|
||||
});
|
||||
});
|
||||
```
|
||||
Then when adding a story, you can then pass a disabled parameter.
|
||||
|
||||
```js
|
||||
// Button.story.js
|
||||
|
||||
import React from 'react';
|
||||
export default {
|
||||
title: 'Button',
|
||||
parameters: {
|
||||
myAddon: { disable: true },
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
#### Styling your addon
|
||||
|
||||
Storybook uses [Emotion](https://emotion.sh/docs/introduction) for styling, AND we provide a theme which can be set by the user!
|
||||
|
||||
We recommend you also use Emotion to style your addon’s components. If you use Emotion, you can use the active storybook theme, which benefits users.
|
||||
If you don’t want to use Emotion, you can use inline styles or another css-in-js lib. You can receive the theme as a prop by using the `withTheme` hoc from Emotion. [Read more about theming](../configure/user-interface#theming).
|
||||
|
||||
#### Storybook components
|
||||
|
||||
You can write your addon UI using any React library, but to make writing addons easier, we’ve published Storybook’s UI components for your reuse as `@storybook/components`. When you use Storybook components you get:
|
||||
|
||||
- Battled tested off-the-shelf components
|
||||
- Storybook native look and feel
|
||||
- Built-in support for Storybook theming
|
||||
|
||||
You can check them out in [Storybook’s own storybook](https://storybookjs.netlify.app/)
|
||||
|
||||
#### Packaging
|
||||
|
||||
In the example above, we showed how to write a local addon inside an existing Storybook project. To distribute your addon more broadly, you can package the addon into a standalone NPM module.
|
||||
|
||||
For a good template of an addon packaged as an NPM module, check out [@storybook/addon-controls].
|
||||
|
||||
It contains addon code similar to what we’ve written above. It also contains:
|
||||
|
||||
- A package.json file that declares the module
|
||||
- Peer dependencies of `react` and `@storybook/addons`
|
||||
-A `register.js` file at the root level written as an ES5 modules
|
||||
- A `src` directory containing the ES6 addon code
|
||||
- A `dist` directory containing transpiled ES5 code on publish
|
||||
|
||||
Your packaged Storybook addon needs to be written in ES5. If you are using ES6, then you need to transpile it.
|
||||
|
||||
When you are developing your addon as a package, you can’t use npm link to add it to your project. Instead add your package as a local dependency into your package.json:
|
||||
|
||||
```json
|
||||
{
|
||||
"dependencies": {
|
||||
"@storybook/addon-controls": "file:///home/username/myrepo"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Addon presets
|
||||
|
||||
[Storybook presets](../presets/introduction) are collections of Storybook configurations that get applied automatically when you create a `/preset.js` entry point in your addon and then list that addon in your project’s [`.storybook/main.js`](../configure/overview#configure-story-rendering) addons field.
|
||||
|
||||
Common uses for presets include:
|
||||
|
||||
- Register an addon’s `register.js`.
|
||||
- Set global parameters for the addon (e.g. [addon-backgrounds](https://github.com/storybookjs/storybook/tree/next/addons/backgrounds)).
|
||||
- Add global decorators for the addon (e.g. [addon-a11y](https://github.com/storybookjs/storybook/tree/next/addons/a11y)).
|
||||
- Set up webpack customizations for the addon (e.g. [addon-docs](../writing-docs/introduction)).
|
||||
|
||||
Here’s an example of typical preset file:
|
||||
|
||||
```js
|
||||
// addon-backgrounds preset.js
|
||||
export function config(entry = []) {
|
||||
return [...entry, require.resolve('./defaultParameters')];
|
||||
}
|
||||
|
||||
export function managerEntries(entries) {
|
||||
return [...entries, require.resolve('./register')];
|
||||
}
|
||||
|
||||
// addon-backgrounds defaultParameters.js
|
||||
export const parameters = {
|
||||
backgrounds: {
|
||||
values: [
|
||||
{ name: 'light', value: '#F8F8F8' },
|
||||
{ name: 'dark', value: '#333333' },
|
||||
],
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
For more information on presets, see the [presets docs](../presets/introduction/index).
|
||||
|
||||
### Writing presets
|
||||
|
||||
If you want to learn more how you can write your own presets, read the [documentation](../presets/writing-presets/index)
|
||||
|
||||
### Addons API
|
||||
|
||||
If you want to expand your knowledge on the Addons API, read the [documentation](/addons/api/index)
|
52
docs/api/cli-options.md
Normal file
52
docs/api/cli-options.md
Normal file
@ -0,0 +1,52 @@
|
||||
---
|
||||
title: 'CLI options'
|
||||
---
|
||||
|
||||
React Storybook comes with two CLI utilities. They are `start-storybook` and `build-storybook`.
|
||||
|
||||
They have some options you can pass to alter the storybook behaviors. We have seen some of them in previous docs.
|
||||
|
||||
Here are all those options:
|
||||
|
||||
## For start-storybook
|
||||
|
||||
```plaintext
|
||||
Usage: start-storybook [options]
|
||||
|
||||
Options:
|
||||
|
||||
--help output usage information
|
||||
-V, --version output the version number
|
||||
-p, --port [number] Port to run Storybook
|
||||
-h, --host [string] Host to run Storybook
|
||||
-s, --static-dir <dir-names> Directory where to load static files from, comma-separated list
|
||||
-c, --config-dir [dir-name] Directory where to load Storybook configurations from
|
||||
--https Serve Storybook over HTTPS. Note: You must provide your own certificate information.
|
||||
--ssl-ca <ca> Provide an SSL certificate authority. (Optional with --https, required if using a self-signed certificate)
|
||||
--ssl-cert <cert> Provide an SSL certificate. (Required with --https)
|
||||
--ssl-key <key> Provide an SSL key. (Required with --https)
|
||||
--smoke-test Exit after successful start
|
||||
--ci CI mode (skip interactive prompts, don't open browser)
|
||||
--quiet Suppress verbose build output
|
||||
--no-dll Do not use dll reference
|
||||
--debug-webpack Display final webpack configurations for debugging purposes
|
||||
```
|
||||
|
||||
## For build-storybook
|
||||
|
||||
```plaintext
|
||||
Usage: build-storybook [options]
|
||||
|
||||
Options:
|
||||
|
||||
-h, --help output usage information
|
||||
-V, --version output the version number
|
||||
-s, --static-dir <dir-names> Directory where to load static files from, comma-separated list
|
||||
-o, --output-dir [dir-name] Directory where to store built files
|
||||
-c, --config-dir [dir-name] Directory where to load Storybook configurations from
|
||||
-w, --watch Enable watch mode
|
||||
--loglevel [level] Control level of logging during build. Can be one of: [silly, verbose, info (default), warn, error, silent]
|
||||
--quiet Suppress verbose build output
|
||||
--no-dll Do not use dll reference
|
||||
--debug-webpack Display final webpack configurations for debugging purposes
|
||||
```
|
79
docs/api/frameworks-feature-support.md
Normal file
79
docs/api/frameworks-feature-support.md
Normal file
@ -0,0 +1,79 @@
|
||||
---
|
||||
title: 'Feature support for frameworks'
|
||||
---
|
||||
|
||||
Storybook integrates with many popular frontend frameworks. We do our best to keep feature parity amongst frameworks, but it’s tricky for our modest team to support every framework.
|
||||
|
||||
Below is a comprehensive table of what’s supported in which framework integration. If you’d like a certain feature supported in your framework, we welcome pull requests.
|
||||
|
||||
### Core frameworks
|
||||
|
||||
Core frameworks have dedicated maintainers or contributors who are responsible for maintaining the integration. As such, you can use most Storybook features in these frameworks.
|
||||
|
||||
| |[React](app/react)|[Vue](app/vue)| [Angular](app/angular) | [HTML](app/html) | [Ember](app/ember) |
|
||||
|--------------------------------------------|:----------------:|:-------------:|:----------------------:|:------------------:|:-------------------:|
|
||||
|Addons | | | | | |
|
||||
| [a11y](addons/a11y) |+ |+ |+ |+ |+ |
|
||||
| [actions](addons/actions) |+ |+ |+ |+ |+ |
|
||||
| [backgrounds](addons/backgrounds) | + |+ |+ |+ |+ |
|
||||
| [cssresources](addons/cssresources) | + |+ |+ |+ |+ |
|
||||
| [design assets](addons/design-assets) | + |+ |+ |+ |+ |
|
||||
| [docs](addons/docs) | + |+ |+ |+ |+ |
|
||||
| [events](addons/events) | + |+ |+ |+ |+ |
|
||||
| [google-analytics](addons/google-analytics)| + |+ |+ |+ |+ |
|
||||
| [graphql](addons/graphql) | + | |+ | | |
|
||||
| [jest](addons/jest) | + |+ |+ |+ |+ |
|
||||
| [knobs](addons/knobs) | + |+ |+ |+ |+ |
|
||||
| [links](addons/links) | + |+ |+ |+ |+ |
|
||||
| [options](addons/options) | + |+ |+ |+ |+ |
|
||||
| [query params](addons/queryparams) | + |+ |+ |+ |+ |
|
||||
| [storyshots](addons/storyshots) | + |+ |+ |+ | |
|
||||
| [storysource](addons/storysource) | + |+ |+ |+ |+ |
|
||||
| [viewport](addons/viewport) | + |+ |+ |+ |+ |
|
||||
| Docs | | | | | |
|
||||
| MDX Stories | + |+ |+ |+ |+ |
|
||||
| CSF Stories | + |+ |+ |+ |+ |
|
||||
| StoriesOf Stories | + |+ |+ |+ |+ |
|
||||
| Source | + |+ |+ |+ |+ |
|
||||
| Notes/Info | + |+ |+ | |+ |
|
||||
| Props table | + |+ |+ |+ |+ |
|
||||
| Props controls | + |+ | | | |
|
||||
| Description | + |+ |+ | |+ |
|
||||
| Inline stories | + |+ | | | |
|
||||
|
||||
|
||||
|
||||
|
||||
### Community frameworks
|
||||
|
||||
Community frameworks have fewer contributors which means they may not be as up to date as core frameworks. If you use one of these frameworks for your job, please consider contributing to its integration with Storybook.
|
||||
|
||||
| |[Mithril](app/mithril)|[Marko](app/marko)|[Svelte](app/svelte)|[Riot](app/riot)|[Preact](app/preact)|[Rax](app/rax)
|
||||
| -------------------------------------------|:---------------------:|:----------------:|:-----------------:|:---------------:|:-----------------:|-----|
|
||||
| [a11y](addons/a11y) |+ |+ |+ |+ |+ |+ |
|
||||
| [actions](addons/actions) |+ |+ |+ |+ |+ |+ |
|
||||
| [backgrounds](addons/backgrounds) | + |+ |+ |+ |+ |+ |
|
||||
| [cssresources](addons/cssresources) | + |+ |+ |+ |+ |+ |
|
||||
| [design assets](addons/design-assets) | + |+ |+ |+ |+ |+ |
|
||||
| [docs](addons/docs) | + |+ |+ |+ |+ |+ |
|
||||
| [events](addons/events) | + |+ | | |+ |+ |
|
||||
| [google-analytics](addons/google-analytics)| + |+ |+ |+ |+ |+ |
|
||||
| [graphql](addons/graphql) | | | | | | |
|
||||
| [jest](addons/jest) | + |+ |+ |+ |+ |+ |
|
||||
| [knobs](addons/knobs) | + |+ |+ |+ |+ |+ |
|
||||
| [links](addons/links) | + | |+ |+ |+ |+ |
|
||||
| [options](addons/options) | + | |+ |+ |+ |+ |
|
||||
| [query params](addons/queryparams) | + |+ |+ |+ |+ |+ |
|
||||
| [storyshots](addons/storyshots) | | |+ |+ | |+ |
|
||||
| [storysource](addons/storysource) | + |+ |+ |+ |+ |+ |
|
||||
| [viewport](addons/viewport) | + |+ |+ |+ |+ |+ |
|
||||
| Docs | | | | | | |
|
||||
| MDX Stories | + |+ |+ |+ |+ |+ |
|
||||
| CSF Stories | + |+ |+ |+ |+ |+ |
|
||||
| StoriesOf Stories | + |+ |+ |+ |+ |+ |
|
||||
| Source | + |+ |+ |+ |+ |+ |
|
||||
| Notes/Info | + |+ |+ | |+ |+ |
|
||||
| Props table | | | | | |+ |
|
||||
| Props controls | | | | | |+ |
|
||||
| Description | | | | | |+ |
|
||||
| Inline stories | | | | | |+ |
|
167
docs/api/new-frameworks.md
Normal file
167
docs/api/new-frameworks.md
Normal file
@ -0,0 +1,167 @@
|
||||
---
|
||||
title: 'Frameworks'
|
||||
---
|
||||
|
||||
**One of Storybook’s most powerful aspects is that it’s architected to support any web framework. It supports React, Vue, Angular, Web Components, Svelte and over a dozen others. This work in progress guide helps you get started on adding new framework support for Storybook.**
|
||||
|
||||
### Scaffolding a new framework
|
||||
|
||||
The first thing to do is scaffold your framework support in its own repo.
|
||||
|
||||
We recommend adopting the same project structure as the Storybook monorepo. That structure contains the framework package (“app/<framework>”) and an example app (“examples/<framework>-kitchen-sink”) as well as other associated documentation and configuration as needed.
|
||||
|
||||
This may seem like a little more hierarchy than what’s necessary. But because the structure mirrors the way Storybook’s own monorepo is structured, you can reuse Storybook’s tooling and it also makes it easier to move the framework into the Storybook into the monorepo at a later point if that is desirable.
|
||||
|
||||
We recommend using `@storybook/html` as a starter framework since it’s the simplest one and doesn’t contain any framework-specific oddities. There is a boilerplate to get you started [here](https://github.com/CodeByAlex/storybook-framework-boilerplate):
|
||||
|
||||
### Framework architecture
|
||||
|
||||
Supporting a new framework in Storybook typically consists of two main aspects:
|
||||
|
||||
1. Configuring the server. In Storybook, the server is the node process that runs when you `start-storybook` or `build-storybook`. Configuring the server typically means configuring babel and webpack in framework-specific ways.
|
||||
|
||||
2. Configuring the client. The client is the code that runs in the browser. Configuring the client means providing a framework-specific story rendering function.
|
||||
|
||||
### Configuring the server
|
||||
|
||||
Storybook has the concept of [presets](../presets/introduction), which are typically babel/webpack configurations for file loading. If your framework has its own file format, e.g. “.vue,” you might need to transform these files into JS files at load time. If you expect every user of your framework to need this, you should add it to the framework. So far every framework added to Storybook has done this, because Storybook’s core configuration is very minimal.
|
||||
|
||||
#### Package structure
|
||||
|
||||
To add a framework preset, it’s useful to understand the package structure. Each framework typically exposes two executables in its `package.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"bin": {
|
||||
"start-storybook": "./bin/index.js",
|
||||
"build-storybook": "./bin/build.js",
|
||||
},
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
These scripts pass an `options` object to `@storybook/core/server`, a library that abstracts all of Storybook’s framework-independent code.
|
||||
|
||||
For example, here’s the boilerplate to start the dev server in `start-storybook`:
|
||||
|
||||
```js
|
||||
import { buildDev } from '@storybook/core/server';
|
||||
import options from './options';
|
||||
|
||||
buildDev(options);
|
||||
```
|
||||
|
||||
Thus the meat of adding framework presets is filling in that options object.
|
||||
|
||||
#### Server options
|
||||
|
||||
As described above, the server `options` object does the heavy lifting of configuring the server.
|
||||
|
||||
Let’s look at the `@storybook/vue`’s options definition:
|
||||
|
||||
```js
|
||||
const packageJson = require('../../package.json');
|
||||
|
||||
export default {
|
||||
packageJson,
|
||||
framework: ‘vue’,
|
||||
frameworkPresets: [require.resolve('./framework-preset-vue.js')],
|
||||
};
|
||||
```
|
||||
<div style="background-color:#F8FAFC">
|
||||
TODO: VET REACT, VUE,WEB COMPONENTS links below
|
||||
</div>
|
||||
|
||||
The value of the `framework` option (in this case ‘vue’) is something that gets passed to addons and allows them to do special case things for your framework.
|
||||
|
||||
The real meat of this file is the framework presets, and these are standard [Storybook presets](../presets/introduction) -- you can look at framework packages in the Storybook monorepo (e.g. [react], [vue], [web-components]) to see examples of framework-specific customizations.
|
||||
|
||||
|
||||
### Configuring the client
|
||||
|
||||
To configure the client, you must provide a framework specific render function. Before diving into the details, it’s important to understand how user-written stories relate to what is finally rendered on the screen.
|
||||
|
||||
#### Renderable objects
|
||||
|
||||
Storybook stories are ES6 functions that return a “renderable object.”
|
||||
|
||||
Consider the following React story:
|
||||
|
||||
```js
|
||||
// Button.story.js
|
||||
import { Button } from './Button';
|
||||
export default {
|
||||
title: 'Button',
|
||||
component: Button
|
||||
}
|
||||
|
||||
export const Sample = () => (
|
||||
<Button label='hello button' />
|
||||
);
|
||||
```
|
||||
|
||||
In this case, the renderable object is the React element, `<Button .../>`.
|
||||
|
||||
In most other frameworks, the renderable object is actually a plain old javascript object.
|
||||
|
||||
Consider the following hypothetical example:
|
||||
|
||||
```js
|
||||
// Button.story.js
|
||||
|
||||
import { Button } from './Button';
|
||||
export default {
|
||||
title: 'Button',
|
||||
component: Button
|
||||
}
|
||||
export const Sample = () => ({
|
||||
template: '<button label=:label />',
|
||||
data: {
|
||||
label: 'hello button'
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
The design of this “renderable object” is framework-specific, and should ideally match the idioms of that framework.
|
||||
|
||||
#### Render function
|
||||
|
||||
The frameworks render function is the thing responsible for converting the renderable object into DOM nodes. This is typically of the form:
|
||||
|
||||
```js
|
||||
const rootElement = document.getElementById('root');
|
||||
|
||||
export default function renderMain({
|
||||
storyFn,
|
||||
}: RenderMainArgs) {
|
||||
const storyObj = storyFn();
|
||||
const html = fn(storyObj);
|
||||
rootElement.innerHTML = html;
|
||||
}
|
||||
```
|
||||
|
||||
#### Package structure
|
||||
|
||||
On the client side, the key file is [`src/client/preview.js`](../configure/overview#configure-story-rendering):
|
||||
|
||||
```js
|
||||
import { start } from '@storybook/core/client';
|
||||
import './globals';
|
||||
import render from './render';
|
||||
const api = start(render);
|
||||
|
||||
// the boilerplate code
|
||||
```
|
||||
|
||||
The globals file typically sets up a single global variable that client-side code (such as addon-provided decorators) can refer to if needed to understand which framework its running in:
|
||||
|
||||
```js
|
||||
import { window } from 'global';
|
||||
window.STORYBOOK_ENV = ‘vue’;
|
||||
```
|
||||
|
||||
<div style="background-color:#F8FAFC">
|
||||
TODO: need location on the render function for the frameworks
|
||||
</div>
|
||||
|
||||
The `start` function abstracts all of Storybook’s framework-independent client-side (browser) code, and it takes the render function we defined above. For examples of render functions, see [React](), [Vue](), [Web-components]() in the Storybook monorepo.
|
497
docs/api/stories.md
Normal file
497
docs/api/stories.md
Normal file
@ -0,0 +1,497 @@
|
||||
---
|
||||
title: 'Stories'
|
||||
---
|
||||
|
||||
### Component Story Format (CSF)
|
||||
|
||||
Storybook's Component Story Format (CSF) is the recommended way to [write stories](../writing-stories/introduction) since Storybook 5.2. [Read the announcement](https://medium.com/storybookjs/component-story-format-66f4c32366df) to learn more about how it came to be.
|
||||
|
||||
<div style="background-color:#F8FAFC">
|
||||
TODO: add advanced readme link and if stories of api needs to be maintained)
|
||||
</div>
|
||||
|
||||
> If you are writing stories in the older `storiesOf()` syntax, you can find documentation in an [advanced README].
|
||||
|
||||
In CSF, stories and component metadata are defined as ES Modules. Every component story file consists of a required **default export** and one or more **named exports**.
|
||||
|
||||
CSF is supported in all frameworks except React Native, where you should use the [storiesOf API](../formats/storiesof-api/index) instead.
|
||||
|
||||
#### Default export
|
||||
|
||||
The default export defines metadata about your component, including the `component` itself, its `title` (where it will show up in the [navigation UI story hierarchy](../writing-stories/docs/writing-stories/naming-components-and-hierarchy#sorting-stories)), [decorators](../writing-stories/decorators), and [parameters](../writing-stories/parameters).
|
||||
|
||||
The `component` field is optional (but encouraged!), and is used by addons for automatic prop table generation and display of other component metadata. `title` should be unique, i.e. not re-used across files.
|
||||
|
||||
```js
|
||||
// MyComponent.story.js
|
||||
import MyComponent from './MyComponent';
|
||||
|
||||
export default {
|
||||
title: 'Path/To/MyComponent',
|
||||
component: MyComponent,
|
||||
decorators: [ ... ],
|
||||
parameters: { ... }
|
||||
}
|
||||
```
|
||||
|
||||
For more examples, see [writing stories](../writing-stories/introduction).
|
||||
|
||||
#### Named story exports
|
||||
|
||||
With CSF, every named export in the file represents a story function by default.
|
||||
|
||||
```js
|
||||
// MyComponent.story.js
|
||||
import MyComponent from './MyComponent';
|
||||
|
||||
export default {
|
||||
title: 'Path/To/MyComponent',
|
||||
component: MyComponent,
|
||||
}
|
||||
|
||||
export const Basic = () => <MyComponent />;
|
||||
export const WithProp = () => <MyComponent prop="value" />;
|
||||
```
|
||||
|
||||
The exported identifiers will be converted to "start case" using Lodash's [startCase](https://lodash.com/docs/#startCase) function. For example:
|
||||
|
||||
| Identifier | Transformation |
|
||||
| ------------------------ |:--------------------:|
|
||||
| **name** |**Name** |
|
||||
| **someName** | **Some Name** |
|
||||
| **someNAME** | **SSome NAME** |
|
||||
| **some_custom_NAME** | **Some Custom NAME** |
|
||||
| **someName1234** | **ome Name 1 2 3 4** |
|
||||
|
||||
|
||||
|
||||
It's recommended to start export names with a capital letter.
|
||||
|
||||
|
||||
Story functions can be annotated with a few different fields to define story-level [decorators](../writing-stories/decorators) and [parameters](../writing-stories/parameters), and also to define the `storyName` of the story.
|
||||
|
||||
The `storyName` is useful if you want to use names with special characters, names that correspond to restricted keywords in Javascript, or names that collide with other variables in the file. If it's not specified, the export name will be used instead.
|
||||
|
||||
|
||||
```jsx
|
||||
export const Simple = () => <MyComponent />;
|
||||
|
||||
Simple.storyName = 'So simple!';
|
||||
Simple.decorators = [ ... ];
|
||||
Simple.parameters = { ... };
|
||||
```
|
||||
|
||||
#### Args story inputs
|
||||
|
||||
Starting in SB 6.0, stories accept named inputs called Args. Args are dynamic data that are provided (and possibly updated by) Storybook and its addons.
|
||||
|
||||
Consider Storybook’s ["Button" example](../writing-stories/introduction#defining-stories) of a text button that logs its click events:
|
||||
|
||||
```js
|
||||
// Button.story.js
|
||||
import { action } from '@storybook/addon-actions';
|
||||
import { Button } from './Button';
|
||||
|
||||
export default {
|
||||
title: 'Button',
|
||||
component: Button
|
||||
};
|
||||
export const Text = () => <Button label=’Hello’ onClick={action('clicked')} />;
|
||||
```
|
||||
|
||||
Now consider the same example, re-written with args:
|
||||
|
||||
```js
|
||||
export const Text = ({ label, onClick }) => <Button label={label} onClick={onClick} />;
|
||||
Text.args = {
|
||||
label: 'Hello',
|
||||
onClick: action('clicked'),
|
||||
};
|
||||
```
|
||||
|
||||
At first blush this might seem no better than the original example. However, if we add the [Docs addon](https://github.com/storybookjs/storybook/tree/master/addons/docs) and configure the [Actions addon](https://github.com/storybookjs/storybook/tree/master/addons/actions) appropriately, we can write:
|
||||
|
||||
```js
|
||||
export const Text = ({ label, onClick }) => <Button label={label} onClick={onClick} />;
|
||||
```
|
||||
|
||||
Or even more simply:
|
||||
|
||||
```js
|
||||
export const Text = (args) => <Button {...args} />;
|
||||
```
|
||||
|
||||
Not only are these versions shorter and easier to write than their no-args counterparts, but they are also more portable since the code doesn't depend on the actions addon specifically.
|
||||
|
||||
For more information on setting up [Docs](../writing-docs/introduction) and [Actions](../essentials/actions), see their respective documentation.
|
||||
|
||||
#### Storybook Export vs Name Handling
|
||||
|
||||
Storybook handles named exports and `story.name` slightly differently. When should you use one vs. the other?
|
||||
|
||||
The named export is always used to determine the story ID / URL.
|
||||
|
||||
If you specify `story.name`, it will be used as the story display name in the UI.
|
||||
|
||||
If you don't specify `story.name`, the named export will be used to generate the display name. Storybook passes the named export through a `storyNameFromExport` function, which is implemented with `lodash.startCase`:
|
||||
|
||||
```js
|
||||
it('should format CSF exports with sensible defaults', () => {
|
||||
const testCases = {
|
||||
name: 'Name',
|
||||
someName: 'Some Name',
|
||||
someNAME: 'Some NAME',
|
||||
some_custom_NAME: 'Some Custom NAME',
|
||||
someName1234: 'Some Name 1234',
|
||||
someName1_2_3_4: 'Some Name 1 2 3 4',
|
||||
};
|
||||
Object.entries(testCases).forEach(([key, val]) => {
|
||||
expect(storyNameFromExport(key)).toBe(val);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
When you want to change the name of your story, just rename the CSF export. This will change the name of the story and also change the story's ID and URL.
|
||||
|
||||
You should use the `story.name` option in the following cases:
|
||||
|
||||
1. You want the name to show up in the Storybook UI in a way that's not possible with a named export, e.g. reserved keywords like "default", special characters like emoji, spacing/capitalization other than what's provided by `storyNameFromExport`.
|
||||
2. You want to preserve the Story ID independently from changing how it's displayed. Having stable Story ID's is useful for integration with third party tools.
|
||||
|
||||
#### Non-story exports
|
||||
|
||||
In some cases, you may want to export a mixture of story and non-stories. For example, it can be useful to export data that's used in your stories.
|
||||
|
||||
To make this possible, you can use optional `includeStories` and `excludeStories` configuration fields in the default export, which can be set to either an array of strings, or a regular expression.
|
||||
|
||||
Consider the following story file:
|
||||
|
||||
```js
|
||||
// MyComponent.story.js
|
||||
import React from 'react';
|
||||
import MyComponent from './MyComponent';
|
||||
import someData from './data.json';
|
||||
|
||||
export default {
|
||||
title: 'MyComponent',
|
||||
component: MyComponent,
|
||||
includeStories: ['SimpleStory', 'ComplexStory']
|
||||
}
|
||||
|
||||
export const simpleData = { foo: 1, bar: 'baz' };
|
||||
export const complexData = { foo: 1, { bar: 'baz', baz: someData }};
|
||||
|
||||
export const SimpleStory = () => <MyComponent data={simpleData} />;
|
||||
export const ComplexStory = () => <MyComponent data={complexData} />;
|
||||
```
|
||||
|
||||
When Storybook loads this file, it will see all the exports, but it will ignore the data exports and only treat `SimpleStory` and `ComplexStory` as stories.
|
||||
|
||||
For this specific example the equivalent result can be achieved in a few ways depending on what's convenient:
|
||||
|
||||
- `includeStories: ['SimpleStory', 'ComplexStory']`
|
||||
- `includeStories: /.*Story$/`
|
||||
- `excludeStories: ['simpleData', 'complexData']`
|
||||
- `excludeStories: /.*Data$/`
|
||||
|
||||
### MDX format
|
||||
|
||||
`MDX` is the syntax [Storybook Docs](../writing-docs/introduction) uses to capture long-form markdown documentation and stories in one file. You can also write pure documentation pages in `MDX` and add them to Storybook alongside your stories. [Read the announcement](https://medium.com/storybookjs/rich-docs-with-storybook-mdx-61bc145ae7bc) to learn more about how and why it came to be.
|
||||
|
||||
#### Basic example
|
||||
|
||||
Let's get started with an example that combines markdown with a single story:
|
||||
|
||||
```js
|
||||
import { Meta, Story, Preview } from '@storybook/addon-docs/blocks';
|
||||
import { Checkbox } from './Checkbox';
|
||||
|
||||
<Meta title="MDX/Checkbox" component={Checkbox} />
|
||||
|
||||
# Checkbox
|
||||
|
||||
With `MDX` we can define a story for `Checkbox` right in the middle of our
|
||||
markdown documentation.
|
||||
|
||||
<Preview>
|
||||
<Story name="all checkboxes">
|
||||
<form>
|
||||
<Checkbox id="Unchecked" label="Unchecked" />
|
||||
<Checkbox id="Checked" label="Checked" checked />
|
||||
<Checkbox appearance="secondary" id="second" label="Secondary" checked />
|
||||
</form>
|
||||
</Story>
|
||||
</Preview>
|
||||
```
|
||||
And here's how that's rendered in Storybook:
|
||||
|
||||
<div style="background-color:#F8FAFC">
|
||||
TODO: VET THIS IMAGE.
|
||||
</div>
|
||||
|
||||
<center>
|
||||
<img src="https://raw.githubusercontent.com/storybookjs/storybook/master/addons/docs/docs/media/mdx-simple.png" width="100%" />
|
||||
</center>
|
||||
|
||||
As you can see there's a lot going on here. We're writing Markdown, we're writing JSX, and somehow we're also defining Storybook stories that are drop-in compatible with the entire Storybook ecosystem.
|
||||
|
||||
Let's break it down.
|
||||
|
||||
#### MDX-Flavored CSF
|
||||
|
||||
[MDX](https://mdxjs.com/) is a standard file format that combines Markdown with JSX. This means you can use Markdown’s terse syntax (such as `# heading`) for your documentation, and freely embed JSX component blocks at any point in the file.
|
||||
|
||||
MDX-flavored [Component Story Format (CSF)](https://medium.com/storybookjs/component-story-format-66f4c32366df) includes a collection of components called **"Doc Blocks"**, that allow Storybook to translate MDX files into storybook stories. MDX-defined stories are identical to regular Storybook stories, so they can be used with Storybook's entire ecosystem of addons and view layers.
|
||||
|
||||
For example, here's the story from `Checkbox` example above, rewritten in CSF:
|
||||
|
||||
```js
|
||||
import React from 'react';
|
||||
import { Checkbox } from './Checkbox';
|
||||
export default { title: "MDX/Checkbox" component: Checkbox };
|
||||
export const allCheckboxes = () => (
|
||||
<form>
|
||||
<Checkbox id="Unchecked" label="Unchecked" />
|
||||
<Checkbox id="Checked" label="Checked" checked />
|
||||
<Checkbox appearance="secondary" id="second" label="Secondary" checked />
|
||||
</form>
|
||||
);
|
||||
```
|
||||
|
||||
There's a one-to-one mapping from the code in `MDX` to `CSF`, which in turn directly corresponds to Storybook's internal `storiesOf` API. As a user, this means your existing Storybook knowledge should translate between the three. And technically, this means that the transformations that happen under the hood are simple and predictable.
|
||||
|
||||
#### Writing stories
|
||||
|
||||
Now let's look at a more realistic example to see a few more things we can do:
|
||||
|
||||
```md
|
||||
import { Meta, Story, Preview } from '@storybook/addon-docs/blocks';
|
||||
|
||||
import { Badge } from './Badge';
|
||||
import { Icon } from './Icon';
|
||||
|
||||
<Meta title="MDX/Badge" component={Badge} />
|
||||
|
||||
# Badge
|
||||
|
||||
Let's define a story for our `Badge` component:
|
||||
|
||||
<Story name="positive">
|
||||
<Badge status="positive">Positive</Badge>
|
||||
</Story>
|
||||
|
||||
We can drop it in a `Preview` to get a code snippet:
|
||||
|
||||
<Preview>
|
||||
<Story name="negative">
|
||||
<Badge status="negative">Negative</Badge>
|
||||
</Story>
|
||||
</Preview>
|
||||
|
||||
We can even preview multiple stories in a block. This
|
||||
gets rendered as a group, but defines individual stories
|
||||
with unique URLs and isolated snapshot tests.
|
||||
|
||||
<Preview>
|
||||
<Story name="warning">
|
||||
<Badge status="warning">Warning</Badge>
|
||||
</Story>
|
||||
<Story name="neutral">
|
||||
<Badge status="neutral">Neutral</Badge>
|
||||
</Story>
|
||||
<Story name="error">
|
||||
<Badge status="error">Error</Badge>
|
||||
</Story>
|
||||
<Story name="with icon">
|
||||
<Badge status="warning">
|
||||
<Icon icon="check" inline />
|
||||
with icon
|
||||
</Badge>
|
||||
</Story>
|
||||
</Preview>
|
||||
```
|
||||
|
||||
And here's how that gets rendered in Storybook:
|
||||
|
||||
<div style="background-color:#F8FAFC">
|
||||
TODO: VET THIS IMAGE.
|
||||
</div>
|
||||
|
||||
<center>
|
||||
<img src="https://raw.githubusercontent.com/storybookjs/storybook/master/addons/docs/docs/media/mdx-page.png" width="100%" />
|
||||
</center>
|
||||
|
||||
## Embedding stories
|
||||
|
||||
Suppose you have an existing story and want to embed it into your docs. Here's how to show a story with ID `some--id` (check the browser URL in Storybook v5+ to see a story's ID):
|
||||
|
||||
```md
|
||||
import { Story } from "@storybook/addon-docs/blocks";
|
||||
|
||||
# Some header
|
||||
|
||||
And markdown here
|
||||
|
||||
<Story id="some--id" />
|
||||
```
|
||||
|
||||
You can also use the rest of the MDX features in conjunction with embedding. That includes source, preview, and prop tables.
|
||||
|
||||
## Decorators and parameters
|
||||
|
||||
To add [decorators](../writing-docs/mdx#decorators-and-parameters) and [parameters](../writing-docs/mdx#decorators-and-parameters) in MDX:
|
||||
|
||||
```js
|
||||
<Meta
|
||||
title='MyComponent'
|
||||
decorators={[ ... ]}
|
||||
parameters={{ ... }}
|
||||
/>
|
||||
|
||||
<Story name="story" decorators={[ ... ]} parameters={{ ... }} >
|
||||
...
|
||||
</Story>
|
||||
```
|
||||
|
||||
In addition, global decorators work just like before, e.g. adding the following to your [`.storybook/preview.js`](../configure/overview#configure-story-rendering):
|
||||
|
||||
```js
|
||||
// .storybook/preview.js
|
||||
import { addDecorator, addParameters } from '@storybook/react';
|
||||
|
||||
addDecorator(...);
|
||||
addParameters({ ... });
|
||||
```
|
||||
|
||||
#### Documentation-only MDX
|
||||
|
||||
Typically, when you use Storybook MDX, you define stories in the MDX documentation is automatically associated with those stories. But what if you want to write Markdown-style documentation and have it show up in your Storybook?
|
||||
|
||||
If you don't define stories in your MDX, you can write MDX documentation and associate it with an existing story, or embed that MDX as its own documentation node in your Storybook's navigation.
|
||||
|
||||
If you don't define a `Meta`, you can write Markdown and associate with an existing story. See ["CSF Stories with MDX Docs"](../writing-docs/mdx).
|
||||
|
||||
To get a "documentation-only story", in your UI, define a `<Meta>` as you normally would, but don't define any stories. It will show up in your UI as a documentation node:
|
||||
|
||||
<div style="background-color:#F8FAFC">
|
||||
TODO: VET THIS IMAGE.
|
||||
</div>
|
||||
|
||||
<center>
|
||||
<img src="https://raw.githubusercontent.com/storybookjs/storybook/master/addons/docs/docs/media/mdx-documentation-only.png" width="100%" />
|
||||
</center>
|
||||
|
||||
## MDX file names
|
||||
|
||||
Unless you use a custom webpack configuration, all of your `MDX` files should have the suffix `*.stories.mdx`. This tells Storybook to apply its special processing to the `<Meta>` and `<Story>` elements in the file.
|
||||
|
||||
Be sure to update your Storybook config file to load `.stories.mdx` stories, as per the [`addon-docs` installation instructions](https://github.com/storybookjs/storybook/tree/master/addons/docs#installation).
|
||||
|
||||
### ArgTypes
|
||||
|
||||
> NOTE: This API is experimental and may change outside of the typical semver release cycle
|
||||
|
||||
ArgTypes are a first-class feature in Storybook for specifying the behaviour of [Args](../writing-stories/args). By specifying the type of an arg you constrain the values that it can take and can also provide information about args that are not explicitly set (i.e. not required).
|
||||
|
||||
You can also use argTypes to “annotate” args with information that is used by addons that make use of those args, for instance to instruct the controls addons to render a color choose for a string-valued arg.
|
||||
|
||||
The most concrete realization of argTypes is the [Props Table](props table) doc block. Each row in the table corresponds to a single argType, as well as the current value of that arg.
|
||||
|
||||
<div style="background-color:#F8FAFC">
|
||||
TODO: image mentioned in SB 6.0 needs to be further vetted
|
||||
</div>
|
||||
|
||||
## Automatic argType inference
|
||||
|
||||
If you are using the Storybook [docs](../writing-docs) addon (installed by default as part of [essentials](../essentials/introduction)), then Storybook will infer a set of argTypes for each story based on the `component` specified in the [default export](#default-export) of the CSF file.
|
||||
|
||||
To do so, Storybook uses various static analysis tools depending on your framework.
|
||||
|
||||
<div style="background-color:#F8FAFC">
|
||||
TODO: further vet this item
|
||||
</div>
|
||||
|
||||
> If you are using React, Storybook uses …, [read details], [open issues].
|
||||
|
||||
The format of the generated argType will look something like:
|
||||
|
||||
```js
|
||||
const argTypes = {
|
||||
label: {
|
||||
name: 'label',
|
||||
type: { name: 'string', required: false },
|
||||
defaultValue: 'Hello',
|
||||
description: 'demo description',
|
||||
table: {
|
||||
type: { summary: 'string' },
|
||||
defaultValue: { summary: 'Hello' },
|
||||
}
|
||||
control: {
|
||||
type: 'text'
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
In this ArgTypes data structure, name, type, defaultValue, and description are standard fields in all ArgTypes (analogous to PropTypes in React). The table and control fields are addon-specific annotations. So, for example, the table annotation provides extra information to customize how label gets rendered, and the control annotation provides extra information for the control for editing the property.
|
||||
|
||||
> NOTE: @storybook/addon-docs provide shorthand for common tasks:
|
||||
> type: 'number' is shorthand for type: { name: 'number' }
|
||||
> control: 'radio' is shorthand for control: { type: 'radio' }
|
||||
|
||||
|
||||
#### Manual specification
|
||||
|
||||
If you want more control over the props table or any other aspect of using argTypes, you can overwrite the generated argTypes for you component on a per-arg basis. For instance, with the above inferred argTypes and the following default export:
|
||||
|
||||
```js
|
||||
|
||||
export default {
|
||||
title: 'Button',
|
||||
component: Button,
|
||||
argTypes: {
|
||||
label: {
|
||||
description: 'overwritten description',
|
||||
table: {
|
||||
type: { summary: 'something short', detail: 'something really really long' },
|
||||
},
|
||||
control: {
|
||||
type: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
These values--description, table.type, and controls.type--get merged over the defaults that are extracted by Storybook. The final merged values would be:
|
||||
|
||||
```js
|
||||
const argTypes = {
|
||||
label: {
|
||||
name: 'label',
|
||||
type: { name: 'string', required: false },
|
||||
defaultValue: 'Hello',
|
||||
description: 'overwritten description',
|
||||
table: {
|
||||
type: { summary: 'something short', detail: 'something really really long' },
|
||||
defaultValue: { summary: 'Hello' },
|
||||
}
|
||||
control: {
|
||||
type: null
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
In particular, this would render a row with a modified description, a type display with a dropdown that shows the detail, and no control.
|
||||
|
||||
#### Using argTypes in addons
|
||||
|
||||
If you want to access the argTypes of the current component inside an addon, you can use the `useArgTypes` hook from the `@storybook/api` package:
|
||||
|
||||
```js
|
||||
import { useArgTypes } from '@storybook/api';
|
||||
|
||||
// inside your panel
|
||||
const { argTypes } = useArgTypes();
|
||||
```
|
@ -21,5 +21,10 @@ module.exports = {
|
||||
prefix: 'writing-docs',
|
||||
pages: ['introduction', 'docs-page', 'mdx', 'doc-blocks'],
|
||||
},
|
||||
{
|
||||
title: 'API',
|
||||
prefix:'api',
|
||||
pages:['stories','addons','new-frameworks','cli-options','frameworks-feature-support']
|
||||
},
|
||||
],
|
||||
};
|
Loading…
x
Reference in New Issue
Block a user