ゼロから始めるStorybook入門(React編)
StorybookはアプリケーションからUIコンポーネントを切り離し、独立した状態でコンポーネントの開発、動作確認やテストを行うために利用することができるUIツールです。コンポーネントが持つpropsを変更することでブラウザ上でどのような表示になるかプレビューを行うことで動作確認することができます。この説明が最初は”?”の人でも本文書では公式のドキュメントよりもさらにシンプルなコードを利用して動作確認を行なっているのでStorybookの基礎的な利用方法や機能を短時間で理解することができます。
Storybookが利用できるJavaScriptのフレームワーク/ライブラリにはReact以外にもVue.js, React Native, Svelte, Amber, Angularなどがありますが本文書ではReactを利用しています。本文書で動作確認を行なっているStorybookのバージョンは6です。
目次
Reactのインストール
create-react-appを利用してreact-storybookという名前のReactプロジェクトを作成します。プロジェクト名は任意の名前をつけてください。
% npx create-react-app react-storybook
プロジェクトの作成完了後、react-storybookフォルダに移動します。
% cd react-storybook
StoryBookのインストール
react-storybookフォルダでnpx storybook@latest initコマンドを実行してstorybookのインストールを行います。
% npx storybook@latestyn init
Need to install the following packages:
storybook
Ok to proceed? (y)
//略
✅ migration check successfully ran
To run your Storybook, type:
yarn storybook
For more information visit: https://storybook.js.org
インストールが完了したらインストールメッセージに表示されている通りyarn strorybookを実行します。起動メッセージにバージョン(6.5.5)が表示されます。本文書ではStorybookのバージョン6.5.5を利用して動作確認を行います。Storybookの最新バージョンは7です。
% yarn strorybook
//略
99% done plugins webpack-hot-middlewarewebpack built preview b835dc28a07a2114afea in 15221ms
╭────────────────────────────────────────────────────╮
│ │
│ Storybook 6.5.5 for React started │
│ 14 s for manager and 17 s for preview │
│ │
│ Local: http://localhost:6006/ │
│ On your network: http://192.168.2.110:6006/ │
│ │
╰────────────────────────────────────────────────────╯
<i> [webpack-dev-middleware] wait until bundle finished: /
99% done plugins webpack-hot-middlewarewebpack built preview 27f8a5b932c644b98baa in 1200ms
インストールが完了するとブラウザが自動で起動しWelcomeページが表示されます。
左のメニューにあるButtonを展開するとLarge, Primary, Secondary, Smallが表示されLargeをクリックすると大きなボタン、Primaryだとブルーカラーのボタン、Smallだと小さなボタンというように同じボタンでも異なるデザインのボタンが表示されます。左のメニューに表示されているButton, Header, Pageなどはコンポーネントに対応する名前で、Buttonの下の階層にあるLarge, Primary,…などをStorybookではストーリーと呼びます。1つのコンポーネントに対して複数のストーリーを設定すると各ストーリーで設定した値を元にサイドメニューの右側のCanvasタブにコンポーネントが描写されます。
左のサイドメニューに表示されているストーリーがどのファイルに記述されているか確認するためにstorybookインストール後のフォルダ構成を確認します。プロジェクトフォルダ(react-storybook)の中にstorybookに関係する2つのフォルダを確認することができます。1つはプロジェクトフォルダ直下に作成される.storybookフォルダ、もう一つはsrcフォルダ直下に作成されるstoriesフォルダです。
ブラウザ上に表示されている内容を記述したファイルはstoriesフォルダの中に保存されているので中身を確認するとメニューに表示されているIntroduction, Button, Header, Pageに対応するファイルを確認することができます。それらのファイルの中でファイル名に*.stories.jsxが含まれているファイルがStorybookのストーリーを記述しているファイルです。
Exampleとして作成されているそれらのファイルを確認することもできますが初めてStorybookを利用する人にとって少しハードルが高いように思われるのでstoriesフォルダの中身はスキップして別のフォルダで一からストーリーを設定していきます。
初めてのStoryBook設定
Buttonコンポーネントの設定
Buttonコンポーネントを利用してStorybookの動作確認をしたいのでsrcフォルダの下にcomponentsフォルダ、componentsの下にButtonフォルダを作成しその下にButton.jsxファイルとButton.stories.jsxファイルを作成します。
Button.jsxファイルに下記の内容を記述します。親コンポーネントからpropsでchildrenを受け取るだけのシンプルなコンポーネントです。
function Button({ children }) {
return <button>{children}</button>;
}
export default Button;
ButtonコンポーネントのストーリーをButton.stories.jsxファイルに記述していきます。ストーリーといっても何か特別なものではなくButtonコンポーネントをどのように描写するのかを決めるJavaScriptの関数です。ストーリーはComponent Story Format(CSF)というフォーマットを利用して記述していきます。
export defaultの中にmetadataであるtitleとcomponentを設定します。titleはサイドメニューのストーリーをまとめていたButton, Header, Pageに対応します。componentはストーリーを設定するコンポーネントを指定します。コンポーネントはimportしておく必要があります。
import Button from './Button';
export default {
title: 'Button',
component: Button,
};
export defaultでmetadataを設定後その下にストーリーを記述します。ストーリーとしてHelloButtonを定義しています。HelloButton関数の中ではButtonタグの間に文字列を挿入し、挿入した文字列はprops.childrenでButtonコンポーネントに渡しています。
import Button from './Button';
export default {
title: 'Button',
component: Button,
};
export const HelloButton = () => <Button>Hello World!</Button>;
これでButtonコンポーネントに対するストーリーの設定は完了です。ブラウザの左側のメニューに追加したButtonの情報が表示されます。
現在の設定ではsrcフォルダ下に存在するstories.jsxファイルが自動で検知されるように設定されているので.storybookフォルダに存在する設定ファイルmain.jsの内容を変更します。
componentsフォルダ下でstories.jsxという名前が入ったファイルのみ自動検知できるように変更しています。
module.exports = {
// "stories": [
// "../src/**/*.stories.mdx",
// "../src/**/*.stories.@(js|jsx|ts|tsx)"
// ],
stories: ['../src/components/**/*.stories.jsx'], //追加
addons: [
'@storybook/addon-links',
'@storybook/addon-essentials',
'@storybook/addon-interactions',
'@storybook/preset-create-react-app',
],
framework: '@storybook/react',
core: {
builder: '@storybook/builder-webpack5',
},
};
設定ファイルのmain.jsを更新した場合はStorybookを再起動する必要があるのでyarn storybookを再実行してください。
Button.stories.jsxファイルの中のmetadataのtitleに設定したButtonは左のサイドメニューに表示されその中にストーリーHello Buttonが表示されます。ストーリーHello Buttonを選択すると”Hello World!”のテキストが入ったボタンが表示されます。
ボタンに表示される文字を変えたい場合には同じファイル(button.stories.jsx)の中に新たにストーリーを追加することでテキストの変更によってどのような違いができるのかを確認することができます。
import Button from '../Button/Button';
export default {
title: 'Button',
component: Button,
};
export const HelloButton = () => <Button>Hello World!</Button>;
export const ClickButton = () => <Button>Click!</Button>;
追加後にブラウザを確認するとClickButtonが追加され左側のメニューに自動で追加され、ClickButtonを選択するとボタンには設定したテキストClick!が表示されます。
titleの変更
titleを変更することでサイドメニューに表示される名前を変更することができますが階層化することも可能です。例えばtitleをButtonからCommon/Buttonに変更します。
export default {
title: 'Common/Button',
component: Button,
};
Buttonの上にCOMMONが表示され階層化されることが確認できます。
titleを設定しない場合はStrobookが自動で設定を行なってくれます。
.storybookフォルダの確認
Storybookをインストール時に作成される.stroybookフォルダにはStrorybookの設定ファイルが保存されています。メインの設定ファイルであるmain.jsファイルを確認するとstoriesプロパティにはstoryファイルのパスが配列で設定されています。この設定によりsrcフォルダ 下の*.stories.jsx/jsが自動で認識されることを先ほど確認しました。addonsプロパティにはStorybookのアドオンが設定されておりStorybookの機能を拡張することができます。その他の設定できる項目についてはStorybookのドキュメントから確認することができます。
module.exports = {
// "stories": [
// "../src/**/*.stories.mdx",
// "../src/**/*.stories.@(js|jsx|ts|tsx)"
// ],
stories: ['../src/components/**/*.stories.jsx'],
addons: [
'@storybook/addon-links',
'@storybook/addon-essentials',
'@storybook/addon-interactions',
'@storybook/preset-create-react-app',
],
framework: '@storybook/react',
core: {
builder: '@storybook/builder-webpack5',
},
};
addonsの確認
addonsによるStorybookの機能の拡張といってもどのようなことが可能なのかわかりにくいかと思います。addonsの影響によってどのような変化があるのか確認しておきましょう。
デフォルトの状態のブラウザ上に表示されるStorybookの画面を見ると上部はさまざまなアイコン、下部にはControlsやActoinsなどのタブを確認することができます。
main.jsファイルを開いてaddonsの中からaddon-essentialsをコメントします。
module.exports = {
// "stories": [
// "../src/**/*.stories.mdx",
// "../src/**/*.stories.@(js|jsx|ts|tsx)"
// ],
stories: ['../src/components/**/*.stories.jsx'],
addons: [
'@storybook/addon-links',
// '@storybook/addon-essentials',
'@storybook/addon-interactions',
'@storybook/preset-create-react-app',
],
framework: '@storybook/react',
core: {
builder: '@storybook/builder-webpack5',
},
};
main.jsを更新した後は設定を反映させるためyarn storybookを再実行する必要があります。
ブラウザを確認するとCanvasの横のDocsが消え、上部に複数表示されていたアイコンの数が5つ消え下部のタグもInteractionsのみの表示になっています。
addonsのessentialは複数のaddonsが含まれており公式ドキュメントから確認することができます。ドキュメントからDocs, Controls, Actions, Viewport, Backgrounds, Toolbars&globals, Measure&outlineが含まれていることがわかります。このことから消えたアイコンや下部のタブはessentialに含まれるaddonsということがわかります。
それぞれの機能がどのような動作を行うのか確認していませんがaddonsによってStorybookの機能拡張が行われていることを理解することができました。再度main.jsを元の状態に戻してaddonsのessentialsを利用できる状態にしてください。yarn storybookの再実行も忘れずに行なってください。
ストーリーの追加
CSSの適用
ButtonコンポーネントにCSSが適用できるようにButtonフォルダの中にbutton.cssファイルを作成してbutton要素に対してCSSを適用します。
button {
font-size: 14px;
padding: 10px;
border: 0;
border-radius: 1em;
cursor: pointer;
display: inline-block;
}
作成したbutton.cssファイルをButton.jsxファイルでimportします。
import './button.css';
function Button({ children }) {
return <button>{children}</button>;
}
export default Button;
CSSが適用されブラウザ上のボタンが変更されます。
propsの値によって背景色を変更できるように新たにpropsにcolorを追加します。デフォルト値をdefaultとします。
import './button.css';
function Button({ children, color = 'default' }) {
return <button className={color}>{children}</button>;
}
export default Button;
defaultのclassをbutton.cssに追加します。button要素の文字の色をwhiteに設定します。
button {
font-size: 14px;
padding: 10px;
border: 0;
border-radius: 1em;
cursor: pointer;
display: inline-block;
color: white;
}
.default {
background-color: #6c757d;
}
ブラウザ上のボタンが下記のように変わります。
propsのcolorの値によってボタンのデザインが変わるようにbutton.stories.jsxで設定したストーリーを変更します。
import Button from '../Button/Button';
export default {
title: 'Common/Button',
component: Button,
};
export const Default = () => <Button>Default</Button>;
export const Primary = () => <Button color="primary">Primary</Button>;
Primary関数の場合にはButtonタグのcolor propsにprimaryを設定していますがprimaryはbutton.cssに存在しないのでbutton.cssに追加します。
//略
.primary {
background-color: #007bff;
}
Primaryのストーリーを選択すると背景色がブルーのボタンが表示されます。
さらに背景色の異なるストーリーDangerを追加することもできます。
export const Default = () => <Button>Default</Button>;
export const Primary = () => <Button color="primary">Primary</Button>;
export const Danger = () => <Button color="danger">Danger</Button>;
CSSの追加も合わせて行います。
//略
.danger {
background-color: #dc3545;
}
Buttonコンポーネントに設定したcolor propsに対応したストーリーを作成し、それぞれのストーリーでcolor propsに渡す値を変えることで渡した値によってどのようにボタンが表示されるかブラウザ上で確認できるようになりました。
サイズの変更
Buttonコンポーネントに背景色だけではなくサイズの変更もできるようにpropsにsizeを追加します。
import './button.css';
function Button({ children, color = 'default', size = 'base' }) {
return <button className={`${color} ${size}`}>{children}</button>;
}
export default Button;
size propsのdefault値はbaseとしています。baseの他にsm, lgも設定できるようにsmとlgもbutton.cssファイルに追加しておきます。文字の大きさだけではなくpaddingも変更するためbutton要素に設定していたpaddingをbase, sm, lgで設定できるように変更します。
button {
border: 0;
border-radius: 1em;
cursor: pointer;
display: inline-block;
color: white;
}
//略
.base {
font-size: 14px;
padding: 10px;
}
.sm {
font-size: 12px;
padding: 8px;
}
.lg {
font-size: 18px;
padding: 14px;
}
Button.stories.jsxにカラーとサイズを指定したストーリー(PrimarySmall, PrimaryLarge)を追加します。
import Button from '../Button/Button';
export default {
title: 'Common/Button',
component: Button,
};
export const Default = () => <Button>Default</Button>;
export const Danger = () => <Button color="danger">Danger</Button>;
export const Primary = () => <Button color="primary">Primary</Button>;
export const PrimarySmall = () => (
<Button size="sm" color="primary">
Small
</Button>
);
export const PrimaryLarge = () => (
<Button size="lg" color="primary">
Large
</Button>
);
設定が完了するとブラウザ上から”Primary Large”と”Primary Small”が選択できるようになります。Primary Largeの場合は大きなボタン、Primary Smallの場合は小さなボタンが表示されます。Primaryの場合は通常のサイズのボタンが表示されます。ストーリーによってサイズの異なるボタンをブラウザ上で確認できるようになりました。
Argsの利用
これまでの設定でもストーリーを設定することができましたがより柔軟にストーリーを記述するためにArgsを利用した記述方法を確認します。Argsはargumentsの略です。
Argsを利用する場合はまず最初にTemplate関数を設定します。argsはコンポーネントに渡すpropsです。
const Template = (args) => <Button {...args} />;
ストーリーのDefault関数をTemplate関数でbindすることで新規の関数として作成します。作成後はargsを利用してpropsの値を設定します。
const Template = (args) => <Button {...args} />
export const Default = Template.bind({});
Default.args = {
children: 'Default',
};
同様の方法でほかのストーリーも変更を行います。
const Template = (args) => <Button {...args} />;
export const Default = Template.bind({});
Default.args = {
children: 'Default',
};
export const Danger = Template.bind({});
Danger.args = {
children: 'Danger',
color: 'danger',
};
export const Primary = Template.bind({});
Primary.args = {
children: 'Primary',
color: 'primary',
};
export const PrimarySmall = Template.bind({});
PrimarySmall.args = {
children: 'Small',
color: 'primary',
size: 'sm',
};
export const PrimaryLarge = Template.bind({});
PrimaryLarge.args = {
children: 'Large',
color: 'primary',
size: 'lg',
};
ブラウザ上に表示されるボタンはArgsに変更する前と何も変わりません。
Argsの値は他のストーリーで利用した値を利用することができます。例えばPrimarySmallとPrimaryLargeはchildren, sizeが異なりcolorは同じ値を設定しています。PrimaryLarge.argsの設定でSpread Operatorを利用してPrimarySmall.argsの値を利用することができます。propsが多い場合には便利です。
export const PrimaryLarge = Template.bind({});
PrimaryLarge.args = {
...PrimarySmall.args,
children: 'Large',
size: 'lg',
};
// export const PrimaryLarge = Template.bind({});
// PrimaryLarge.args = {
// children: 'Large',
// color: 'primary',
// size: 'lg',
// };
Storybookが持つ機能の確認
Controlsの利用
Argsの設定を行うと下部に表示されていたControlsが利用できるようになります。
Controlsを利用することで現在ブラウザ上に表示されているボタンを変更することができます。下記の画面ではPrimaryを選択しているのでストーリーを選択した直後では背景色はブルーでボタンの文字列はPrimaryですがControlsを通して設定を変更することで背景色が赤でボタンの文字列をPrimaryDangerに変更することができます。
DefaultのストーリーをArgsを利用しない場合のDefault関数に戻すとControlsは下記のように表示され変更することはできません。
export const Default = () => <Button>Default</Button>;
PropTypesによる設定
Argsによってボタンに表示されるテキストやcolorをブラウザ上で変更できるようになりました。しかしcolor, sizeは手動で入力する必要があります。
React/JavaScriptの場合PropTypesを設定することでpropsで渡される型の設定を行うことができます。
実際にButton.jsxファイルの中でPropTypesを設定することでcolorやsizeで選択できる値に制限をかけることができます。下記ではsizeはsm, base, lg, colorはprimary, default, dangerのみ選択できるように設定しています。
import './button.css';
import PropTypes from 'prop-types';
function Button({ children, color = 'default', size = 'base' }) {
return <button className={`${color} ${size}`}>{children}</button>;
}
export default Button;
Button.propTypes = {
color: PropTypes.oneOf(['primary', 'default', 'danger']),
size: PropTypes.oneOf(['sm', 'base', 'lg']),
};
ブラウザで確認すると先ほどまでとは異なり、ラジオボタンでcolor, sizeを選択できるようになりました。ラジオボタンで別のcolorを選択すると即座にボタンの色が変わります。
ラジオボタンの変更すると変更した値はCanvasのボタンの描写に反映されます。
argTypesによる設定
PropTypesではなくButton.stories.jsxファイルでargTypesを利用することでcolor, sizeをControlsで変更できるようにすることもできます。
import Button from './Button';
export default {
title: 'Common/Test',
component: Button,
argTypes: {
color: {
options: ['primary', 'default', 'danger'],
control: { type: 'radio' },
},
size: {
options: ['sm', 'base', 'lg'],
control: { type: 'radio' },
},
},
};
//略
Button.jsxファイルで追加したButton.propTypesを削除しても下記のように表示されます。
typeをradioからselectに変更するとラジオボタンではなくセレクトボックスに変更されます。
import Button from './Button';
export default {
title: 'Common/Test',
component: Button,
argTypes: {
color: {
options: ['primary', 'default', 'danger'],
control: { type: 'select' },
},
size: {
options: ['sm', 'base', 'lg'],
control: { type: 'select' },
},
},
};
//略
下記ではcolorを選択しています。
typeをradioに設定するとラジオボタン、selectに設定するとセレクトボックスで値を設定することができました。他にもtypeにはcolorを設定することができます。
Buttonコンポーネントのpropsに新たにボタンの背景を設定できるbackgroundColorを追加します。backgroundColorの値が渡された場合のみ設定が行われます。
import './button.css';
function Button({
children,
color = 'default',
size = 'base',
backgroundColor,
}) {
return (
<button
className={`${color} ${size}`}
style={backgroundColor && { backgroundColor }}
>
{children}
</button>
);
}
export default Button;
argTypesでbackgroundColorのtypeに”color”を選択します。
export default {
title: 'Common/Button',
component: Button,
argTypes: {
color: {
options: ['primary', 'default', 'danger'],
control: { type: 'radio' },
},
size: {
options: ['sm', 'base', 'lg'],
control: { type: 'select' },
},
backgroundColor: {
control: { type: 'color' },
},
},
};
color pickerが表示されカラーを選択することができます。
typeにはこの他にはtext, date, boolean, numberなどあるのでドキュメントのControlsのAnnotationを確認してください。
Actionsの確認(PropTypes)
ButtonコンポーネントにonClickイベントを設定しpropsから渡された関数を設定した場合にActionsによりイベントを検知することでボタンのonClickイベントが正常に動作するのかどうか確認することができます。
propsで渡されるonClick関数をButtonコンポーネントのbutton要素のonClickイベントに設定します。
import './button.css';
import PropTypes from 'prop-types';
function Button({ children, color = 'default', size = 'base', onClick }) {
return (
<button className={`${color} ${size}`} onClick={onClick}>
{children}
</button>
);
}
export default Button;
Button.propTypes = {
color: PropTypes.oneOf(['primary', 'default', 'danger']),
size: PropTypes.oneOf(['sm', 'base', 'lg']),
};
Actionsが正常に動作している場合はボタンをクリックすると下部のActionsタブにメッセージが表示されますが現在の設定では何も反応がありません。
PropTypesの設定にonClickを追加します。
Button.propTypes = {
color: PropTypes.oneOf(['primary', 'default', 'danger']),
size: PropTypes.oneOf(['sm', 'base', 'lg']),
onClick: PropTypes.func.isRequired,
};
再度ボタンをクリックするとクリックに発火するイベントを検知してメッセージが表示されます。下記では3回ボタンをクリックしています。
関数の名前をonClickとしていましたが名前は必ずしもonClickではないのでpropsで渡す関数の名前をonClickからhandleClickに変更します。
//略
function Button({ children, color = 'default', size = 'base', handleClick }) {
return (
<button className={`${color} ${size}`} onClick={handleClick}>
//略
変更するとまたonClickイベントが検知できなくなります。その理由は.storybookフォルダのStorybookの設定ファイルの一つであるpreview.jsファイルにactionsに関する設定が行われておりonから開始する関数名のみ検知することができるためです。
export const parameters = {
actions: { argTypesRegex: "^on[A-Z].*" },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
}
handleClick関数の名前を変えず、preview.jsファイルの設定を変えずにclickイベントを検知できるように設定を行います。ButtonコンポーネントのhandleClick関数だけ検知できようにするためには以下のようにmetadataの中にargTypesの設定を行うことで実現できます。
export default {
title: 'Common/Button',
component: Button,
argTypes: { handleClick: { action: 'clicked' } },
};
argTypesの中のactionで指定した’clicked’はActionsタブのメッセージの先頭に表示されます。
actionの値をtrueに変更すると関数名が表示されます。
export default {
title: 'Common/Button',
component: Button,
argTypes: { handleClick: { action: true } },
};
1回目は先ほどの”clicked”設定時のメッセージで2回目がactionの値をtrueにした後のメッセージです。上のメッセージの方が新しいメッセージです。
Actionsの確認(argTypes)
先ほどはpropTypesにonClickを追加しましたがActionsの設定はButton.stories.jsxのみで設定を行うこともできます。ButtonコンポーネントにonClickイベントを設定しpropsから渡された関数を設定した場合にActionsによりボタンのクリックを検知することができます。
import './button.css';
function Button({ children, color = 'default', size = 'base', onClick }) {
return (
<button className={`${color} ${size}`} onClick={onClick}>
{children}
</button>
);
}
export default Button;
argTypesにonClickを設定します。
export default {
title: 'Common/Button',
component: Button,
argTypes: {
color: {
options: ['primary', 'default', 'danger'],
control: { type: 'radio' },
},
size: {
options: ['sm', 'base', 'lg'],
control: { type: 'radio' },
},
onClick: {
action: 'clicked',
},
},
};
設定後ボタンをクリックするとクリックを検知してメッセージが表示されます。下記では3回ボタンをクリックしています。
propsで渡す名前をonClickからhandleClickに変更します。
import './button.css';
function Button({ children, color = 'default', size = 'base', handleClick }) {
return (
<button className={`${color} ${size}`} onClick={handleClick}>
{children}
</button>
);
}
export default Button;
変更した場合はargTypesでもonClickからhandleClickに変更する必要があります。
export default {
title: 'Common/Button',
component: Button,
argTypes: {
//略
handleClick: {
action: 'clicked',
},
},
};
actionの値をtrueに変更すると関数名が表示されます。
export default {
title: 'Common/Button',
component: Button,
argTypes: {
//略
handleClick: {
action: true,
},
},
};
1回目は先ほどの”clicked”設定時のメッセージで2回目がactionの値をtrueにした後のメッセージです。上のメッセージの方が新しいメッセージです。
argTypesを利用しないでActions
metadataのargTypesを利用せず@storybook/addon-actions’からimportしたactionを利用してActionsタグにメッセージを表示させることができます。
import { action } from '@storybook/addon-actions';
import Button from './Button';
export default {
title: 'Common/Test',
component: Button,
argTypes: {
color: {
options: ['primary', 'default', 'danger'],
control: { type: 'radio' },
},
size: {
options: ['sm', 'base', 'lg'],
control: { type: 'select' },
},
},
};
const something = action('something');
const Template = (args) => {
const handleClick = () => {
something();
};
return <Button {...args} handleClick={handleClick} />;
};
//略
利用しているBottonコンポーネントの設定は下記の通りです。
import './button.css';
function Button({ children, color = 'default', size = 'base', handleClick }) {
return (
<button className={`${color} ${size}`} onClick={handleClick}>
{children}
</button>
);
}
export default Button;
設定後ブラウザ上のボタンをクリックするとactionの引数に設定した文字列が表示されます。
something関数の引数にevent(e)を設定するとActionsのメッセージにイベントの情報も表示されます。
import { action } from '@storybook/addon-actions';
//略
const something = action('something');
const Template = (args) => {
const handleClick = (e) => {
something(e);
};
return <Button {...args} handleClick={handleClick} />;
};
引数は複数設定することができます。
const handleClick = (e) => {
something(e, 'test');
};
ボタンをクリック後にActionsのタブを確認すると以下のメッセージが表示されます。eventと文字列’test’の情報が含まれています。
LinkToの設定
ブラウザ上のあるストーリーから別のストーリーに移動する際にLinkToを利用することができます。
ボタンをクリックすると移動できるように設定を行っていきます。利用するボタンコンポーネントは下記の通りです。
import './button.css';
function Button({ children, color = 'default', size = 'base', handleClick }) {
return (
<button className={`${color} ${size}`} onClick={handleClick}>
{children}
</button>
);
}
export default Button;
handleClickを利用します。linkToの第一引数には移動先のコンポーネントのtitleを設定、第2引数には移動先のストーリーの名前を設定しています。
import { linkTo } from '@storybook/addon-links';
//略
const Template = (args) => {
return <Button {...args} handleClick={linkTo('Common/Button', 'Danger')} />;
};
export const Default = Template.bind({});
Default.args = {
children: 'Default',
};
以上でLinkToの設定は完了です。
Defaultのストーリーに表示されているボタンをクリックすつとDangerのストーリーに移動します。Templateに設定を行っているためDefault以外のストーリーでもボタンをクリックするとすべて Dangerのストーリーに移動します。
Interactionsの設定
Interactionsはストーリーでコンポーネントが描写された後にユーザによるインタラクションをシュミレートしたい時に利用することができるaddonです。ボタンコンポーネントで利用した場合はストーリーの中でボタンをクリックすることができます。
Interactionsではtesting-library、jestの機能を利用します。そのほかにaddonsの@storybook/addon-interactionsが必要になります。デフォルトで@storybook/addon-interactionsとstorybook/testing-libraryのインストールは行われているのでstorybook/jestのみ追加でインストールします。jestの機能を利用しない場合は必要ありません。ここではjestのexpect関数を利用するのでインストールしています。
% npm install @storybook/jest
InterationsはPrimaryLargeストーリーを利用して行います。Interactionsの設定は下記のようにplay関数の中に記述します。引数でargsとcanvasElementを受け取ることができます。
export default {
title: 'Common/Button',
component: Button,
argTypes: {
//略
handleClick: {
action: true,
},
},
};
const Template = (args) => {
return <Button {...args} />;
};
//略
PrimaryLarge.play = async ({ args, canvasElement }) => {
//処理
};
PrimaryLarge.playの関数の中でconsole.logを利用してargsとcanvasElementの中身を確認しておきます。今回のButtonコンポーネントであればargsは以下のオブジェクトです。
{
children: "Large"
color: "primary"
handleClick: ƒ ()
size: "lg"
}
canvasElementはidにrootの値を持つdiv要素で囲まれたButtonコンポーネントのDOMです。
<div id="root">
<button class="primary lg">Large</button>
</div>
button要素に取得する際に利用するgetByRoleメソッドを利用するためにwithin関数の引数にcanvasElementを指定します。取得したボタンのクリックイベントを実行するためにuserEventのclickメソッドを実行します。実行後にhandleClick関数が実行されたかMatcher関数のtoHaveBeenCalled関数でチェックを行なっています。
import Button from './Button';
import { userEvent, within } from "@storybook/testing-library";
import { expect } from '@storybook/jest';
//略
PrimaryLarge.play = async ({ args, canvasElement }) => {
const canvas = within(canvasElement);
await userEvent.click(canvas.getByRole('button'));
await expect(args.handleClick).toHaveBeenCalled();
};
ブラウザ上でPrimaryLargeストーリーを選択した後にplay関数が実行され下部のInterationsのタブに2件のメッセージが表示されます。
Actionsのタグを見るとクリックが行われているので1件メッセージが表示されます。
ここでのInteractionsはクリックを実行し、クリックが実行されたかどうかのチェックを行っているだけですがInteractionsによってボタンの見た目が変わるようにButton.jsxファイルの設定を変更します。
useState Hookでmessageを定義します。handleClick関数はpropsで渡されるのではなくButtonコンポーネントの中で定義し、実行するとclickedがmessageに設定されるようにしています。
import './button.css';
import PropTypes from 'prop-types';
import { useState } from 'react';
function Button({ children, color = 'default', size = 'base' }) {
const [message, setmessage] = useState('');
const handleClick = () => {
setmessage('clicked');
};
return (
<button className={`${color} ${size}`} onClick={handleClick}>
{children} {message}
</button>
);
}
export default Button;
Button.propTypes = {
color: PropTypes.oneOf(['primary', 'default', 'danger']),
size: PropTypes.oneOf(['sm', 'base', 'lg']),
};
PrimaryLargeストーリーのplay関数の中ではbuttonをクリックする設定のみ残してexpect関数によるチェックは削除しています。
PrimaryLarge.play = async ({ canvasElement }) => {
const canvas = within(canvasElement);
await userEvent.click(canvas.getByRole('button'));
};
ブラウザでPrimaryLargeストーリーを選択するとボタンはLarge clickedと表示されます。先程よりも描写されているボタンに変化があるのでInterationsを利用することでユーザとのインタラクションを行えるという説明も理解できるかと思います。
Canvasの表示設定
Parametersの設定
デフォルトの状態ではボタンが表示されているCanvasの背景色はlightかdarkのみ選択することができます。
lightに設定されているのでdarkに変更すると背景色が暗くなります。
light, dark以外の背景色を設定したい場合にParametersを以下のように設定することができます。Primaryのストーリーのみで選択できるように設定を行っており、選択できる背景色はred, green, blueです。
export const Primary = Template.bind({});
Primary.args = {
children: 'Primary',
color: 'primary',
};
Primary.parameters = {
backgrounds: {
values: [
{ name: 'red', value: '#f00' },
{ name: 'green', value: '#0f0' },
{ name: 'blue', value: '#00f' },
],
},
};
設定後light, darkではなくred, green, blueが選択できるようになります。
実際にgreenを選択します。背景色がgreenになることが確認できます。
個別のストーリーではなくButton内のすべてのストーリーでParametersの選択を行いたい場合はexport defaultのmetadataの中で設定を行うことができます。
export default {
title: 'Common/Button',
component: Button,
parameters: {
backgrounds: {
values: [
{ name: 'red', value: '#f00' },
{ name: 'green', value: '#0f0' },
{ name: 'blue', value: '#00f' },
],
},
},
};
Buttonコンポーネントに含まれるストーリーで背景色の設定が可能になります。
Decoratorsの設定
Decoratorsを利用することでCanvasに表示されているButtonコンポーネントの外側にスタイルを設定することができます。
現在左上に表示されているButtonコンポーネントをCanvasの真ん中に表示したい場合には以下のようにDecoratorsを利用することで実現することができます。
export default {
title: 'Common/Button',
component: Button,
decorators: [
(Story) => (
<div
style={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
height: '100vh',
}}
>
<Story />
</div>
),
],
};
ブラウザ上ではボタンが中央に表示されることが確認できます。
Layoutの設定
Decoratorsを利用することでButtonコンポーネントの外側にスタイルを設定することができました。style属性で設定を行いましたが中央に表示するのであればparametersのlayoutを利用することができます。
export default {
title: 'Common/Button',
component: Button,
parameters: {
layout: 'centered',
},
};
centeredを設定することで下記のように表示されます。
設定値には”centered”以外にデフォルトの”padded”と”fullscreen”があります。paddedではpaddingが設定されていますがfullscreenにするとpaddingがなくなります。
export default {
title: 'Common/Button',
component: Button,
parameters: {
layout: 'fullscreen',
},
};
fullscreenでは下記のように表示されます。
Docsの設定
これまでCanvasのみ表示していましたがCanvasの右側にDocsがありこの機能もaddonsにより実現されています。addonsの@storybook/addon-essentialsに含まれています。
Docsではコンポーネントに関する情報を確認することができます。PropsTypeで設定したデフォルト値やcolor, sizeの選択項目も確認することができます。
Button.stories.jsxファイルのexport defaultに設定したcomponentをコメントすると表示されなくなります。
export default {
title: 'Common/Button',
// component: Button,
};
DescriptionやDefault値が表示されていません。
表示される情報を増やしたい場合はButton.jsxにコメントを追加することでDocsに表示されます。
import './button.css';
import PropTypes from 'prop-types';
/**
* Button Component for explanation of StoryBook
*/
function Button({ children, color = 'default', size = 'base', handleClick }) {
//略
記述した”Button Component for explanation of StoryBook”が表示されます。
propTyesの上にコメントを追加すると追加したコメントがDescriptionの中に表示されます。
Button.propTypes = {
/**
* What background color to use
*/
color: PropTypes.oneOf(['primary', 'default', 'danger']),
/**
* How large should the button be?
*/
size: PropTypes.oneOf(['sm', 'base', 'lg']),
};
colorのDescriptionの中に”What background color to use”、sizeのDescriptionの中に”How large should the button be?”が表示されます。
コメントについてはButton.stories.jsxファイルでも設定することができます。
export default {
title: 'Common/Button',
component: Button,
parameters: {
docs: {
description: {
component: '説明用のボタンコンポーネント',
},
},
},
};
記述した内容が表示されます。
Exampleの確認
main.jsファイルのstoriesの値を最初の値に戻します。
module.exports = {
stories: ['../src/**/*.stories.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'],
// stories: ['../src/components/**/*.stories.jsx'],
addons: [
'@storybook/addon-links',
'@storybook/addon-essentials',
'@storybook/addon-interactions',
'@storybook/preset-create-react-app',
],
framework: '@storybook/react',
core: {
builder: '@storybook/builder-webpack5',
},
};
これで作成したButtonコンポーネントも含めて本プロジェクトに含まれるストーリーがすべて表示されます。
本文書を読み終えた人であれば最初にstoriesフォルダのファイルに記述されていた内容がわからなかった人も理解できるようになっているはずです。ぜひ確認してもらいさらにStorybookの理解を深めてください。