React or Next.jsのプロジェクトに Storybook
を追加で導入します。
記事内ではNext.jsで進めますが、Storybookの導入はReactでも一緒になります。
公式のチュートリアルがあるのでそちらを参考にやってみます。 Introduction to Storybook
GitHubに Next.js + Storybook の参考用として作成したプロジェクトがあるのでよければ参考にしてみて下さい!
検証した環境
1 | next | 10.1.3 |
2 | @storybook/react | 6.2.2 |
導入前の前提
Next.jsのプロジェクトは以下のようにして作成しています
TypeScriptだけ導入しておきたかったので--example with-typescript
を指定。
$ yarn create next-app --example with-typescript nextjs-with-storybook
この状態で起動し、
$ yarn run dev
$ next
ready - started server on 0.0.0.0:3000, url: http://localhost:3000
・・・
http://localhost:3000/ にアクセスすると以下のように表示されます。
試しのコンポーネントを追加
Storybookで試すためのコンポーネントを追加します
bgc
というPropsを渡す事で背景色を変えられるだけのシンプルなボタン。
Props
をexportしているのは後ほどのStorybookで使用するためです
import React from 'react'
export type LinkButtonProps = {
bgc?: string
}
const LinkButton = ({ bgc = '#4752ff' }: LinkButtonProps) => (
<a style={{backgroundColor: bgc, padding: '10px 30px', color: 'white'}}>ボタン</a>
)
export default LinkButton
Storybookを導入する
本題です!
Storybookに必要なパッケージをインストール
Storybookの導入方法は公式に「Install Storybook」というページがあるためそちらを参考にやっていきます。
まずインストールを行うためnpx sb init
を実行します。
$ npx sb init
sb init - the simplest way to add a Storybook to your project.
・・・
・・・
✨ Done in 0.56s.
To run your Storybook, type:
yarn storybook
インストールが完了すると、様々なパッケージやnpm-scriptsが自動的に追加されます。
(npm-scriptsも自動なのはありがたい!)
私の環境だと
npm-scriptsのコマンドが2つ、Storybookに関するパッケージ+babelが追加されました。
"scripts": {
"storybook": "start-storybook -p 6006",
"build-storybook": "build-storybook"
},
"devDependencies": {
"@babel/core": "^7.13.14",
"@storybook/addon-actions": "^6.2.2",
"@storybook/addon-essentials": "^6.2.2",
"@storybook/addon-links": "^6.2.2",
"@storybook/react": "^6.2.2",
"babel-loader": "^8.2.2",
}
またStorybookの設定ファイル、参考用のファイル群も自動的に追加されます。
npxについての余談
そもそもnpx
てローカルパッケージの中のコマンドを実行するんじゃなかったっけ?
と思って調べてみたところ
npxコマンドをたたくと次の順番でコマンドを探します。
- ローカルパッケージ(node_modules/.bin)
- 環境変数PATH
- npmレジストリ
なるほど、今回のケースだと
↑で書かれている 「3. npmレジストリ」のコマンドを使用しsb init
を実行しているですね!
Storybookの起動
先ほど自動で追加されたコマンドを実行します
$ yarn run storybook
コマンドを実行すると自動でブラウザが立ち上がります
Storybookが見られるようになりました!
自動でブラウザが立ち上がらない場合は
$ yarn run storybook
が実際に実行している
$ start-storybook -p 6006
の -p
以降のポート番号を調べブラウザにアクセスします。
↑のデフォルトのケースであれば
http://localhost:6006/ にアクセスする事で見られます
Storybookにコンポーネントを表示する
先ほど作成したコンポーネントをStorybookで表示出来るようにします
Storybook導入時に参考として入ってるコンポーネントが書き方/使い方共にとても参考になります。
私がNext.js + Storybookの参考用に作成したリポジトリに残しているので良ければ参考にしてみて下さい。
https://github.com/yuki-takara/nextjs-with-storybook/blob/master/stories/Button.stories.tsx
Storybook用のファイルを作成
Storybookに表示するためには、
Storybookに表示するコンポーネントとは別にStorybook専用のファイルを作ります。
「LinkButton.tsx」と同じ階層に「LinkButton.stories.tsx」を作成します。
Storybookのファイル名はコンポーネント名.stories.tsx
とするのが慣習なためそれに準じています。
ファイルの作成場所は、最終的にファイルのパスを.storybook/main.js
で指定するだけなのでどこでも問題ありません。
私の印象だとコンポーネントと同階層に配置するケースが多いイメージなので、
ここでは同じ階層に作っていきます。
LinkButton.stories.tsxの内容は以下のようにしました
(コードが難しいですね…後ほど紐解いていきます)
import React from 'react'
import { Story, Meta } from '@storybook/react'
import LinkButton, { LinkButtonProps } from './LinkButton'
export default {
title: 'My/Button',
component: LinkButton
} as Meta
const Template: Story<LinkButtonProps> = (args) => <LinkButton {...args} />
export const DefaultButton = Template.bind({})
DefaultButton.args = {}
次に「.storybook/main.js」に作成したファイルを読み込む設定を追記します
module.exports = {
"stories": [
"../stories/**/*.stories.mdx",
"../stories/**/*.stories.@(js|jsx|ts|tsx)",
// ↓の行を追加
"../components/**/*.stories.@(js|jsx|ts|tsx)"
],
"addons": [
"@storybook/addon-links",
"@storybook/addon-essentials"
]
}
これで$ yarn run storybook
でStorybookを立ち上げてみると
コンポーネントがStorybookに表示されました!
stories.tsx/jsxの書き方などは公式ドキュメントがかなりキレイにまとまっているため、
つまづいた時などオススメです!
ここからはLinkButton.stories.tsxに書いた内容を見ていきます
Storybookに表示する大まかな設定(メタデータ)を書く
公式の下記ページ(英語)が分かりやすくまとまっています
https://storybook.js.org/docs/react/api/csf#default-export
まずexport default
を用いて
Storybookのサイドバーに表示する内容、そして使用するコンポーネントなどメタデータを定義します
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), decorators, and parameters.
デフォルトのエクスポートでは、コンポーネント自体、そのタイトル(ナビゲーションUIストーリー階層に表示される場所)、デコレーター、パラメーターなど、コンポーネントに関するメタデータが定義されます。(Google日本語訳)
export default {
title: 'My/Button',
component: LinkButton
} as Meta
プロパティ | 内容 |
---|---|
title | Storybookのサイドバーに表示されるタイトル |
component | 使用するコンポーネント |
他にも指定出来るプロパティは色々ありますが、
まず title
・component
を抑えておければStorybookに表示/操作が行えるようになります。
title
は/
を用いる事で階層を作る事が出来ます。
コードを追っていくとExampleとしてtitle: 'Design System/Atoms/Button'
と書かれていて、
これを実際に試してみると
「Atoms」というフォルダが切られています!
コンポーネントが増えて時、グループ分けしたい際にはぜひ使用したい機能ですね ☺️
コンポーネントの設定
こちらも公式が分かりやすくまとめてくれています
https://storybook.js.org/docs/react/api/csf#named-story-exports
次にexport const
を使い、Storybook上に実際に描画されるコンポーネントの設定を記載します
// 1. Storybookで描画するためのコンポーネントの雛形を用意しておく
const Template: Story<LinkButtonProps> = (args) => <LinkButton {...args} />
// 2. bindを用いて雛形を元にしたコピーを作成
export const DefaultButton = Template.bind({})
// 3. Propsに値を設定しない
DefaultButton.args = {}
Storybookに複数のコンポーネントを表示する場合
例えば、背景色を赤くするコンポーネントも合わせて表示する場合以下のように書きます
const Template: Story<LinkButtonProps> = (args) => <LinkButton {...args} />
// Propsを何も渡さないボタン
export const DefaultButton = Template.bind({})
DefaultButton.args = {}
// Propsを用いて背景色を赤くしたボタン
export const RedButton = Template.bind({})
RedButton.args = {
bgc: '#f00'
}
型定義・bind
と難しい箇所が多いですね、、
bind
を使っている理由は関数の定義をいちいちしなくて良くするため、です。
もしbind
を使わずに書く場合、
export const DefaultButton: Story = () => <LinkButton />
export const RedButton: Story<LinkButtonProps> = (args: LinkButtonProps) => <LinkButton bgc={args.bgc} />
RedButton.args = {
bgc: '#f00'
}
このように描画するコンポーネント毎に関数を書く必要が出てきます。
今回のようにコンポーネントが2つだけ、Propsも少ない場合はいいですが、
これ以上に記載が必要になってくるようであればbind
を使った方が可読性良くキレイに書けます。
Storybookでの表示をカスタマイズする(Optional)
最後にここまでの設定から更にStorybookを便利に利用する方法をご紹介します
アドオンのcontrolsの操作を変更する
StorybookにはStorybookの機能を拡張し便利にするアドオンというものがあります。
$npx sb init
をした際にそのアドオンでよく使われるものをひとまとめにした @storybook/addon-essentials
というパッケージが自動的にインストールされて使える状態になっています!
(ありがたや!!)
https://storybook.js.org/docs/react/essentials/introduction
その中の1つにControls
というコンポーネントの振る舞いをStorybook上で確認出来る非常に便利なアドオンがあります。
現状私が先ほど追加したコンポーネントの背景色のコンポーネント部分は、
特に何も設定していないため文字列を指定するだけのものになっています。
これをカラーピッカーに変更するにはexport default
している部分のargTypes
に指定します
export default {
title: 'Design System/Atoms/Button',
component: LinkButton,
argTypes: {
bgc: { control: 'color' },
},
} as Meta
他にもセレクトボックスやラジオボタンなど色々な選択方法があり、
使い方も含め公式ページに細かく記載されています
https://storybook.js.org/docs/react/essentials/controls#annotation
例えば、先ほどの色の選択を無理矢理セレクトボックスを使って以下のようにする事も出来ます
export default {
title: 'Design System/Atoms/Button',
component: LinkButton,
argTypes: {
bgc: {
options: ['#f00', '#0f0', '#00f'],
control: { type: 'select' }
},
},
} as Meta
storybookに表示される名前を変更する
表示するコンポーネントの storyName
に名前を設定する事でStorybookで表示される名前を変更する事が出来ます
export const DefaultButton = Template.bind({})
DefaultButton.args = {}
DefaultButton.storyName = 'デフォルトのボタン'
export const RedButton = Template.bind({})
RedButton.args = {
bgc: '#f00'
}
RedButton.storyName = '赤いボタン'
描画するコンポーネントを別コンポーネントやタグでラップする
Decorators
を使います
https://storybook.js.org/docs/react/writing-stories/decorators
Decorators
を使うことで
- 別のコンポーネントをimportしてラップする
- divタグで囲んでstyleを当てた上で表示する
といった事が出来ます。
例えば、背景色を黒くした上に表示した場合を確認する、といった事も簡単に実現出来ます
export const DefaultButton = Template.bind({})
DefaultButton.args = {}
DefaultButton.decorators = [
Story => (
<div
style={{ backgroundColor: '#000', height: '100px', display: 'flex', alignItems: 'center' }}
>
<Story />
</div>
),
]