Ember.jsでもStorybookを利用することが出来ます。
参考用ソースコード:https://github.com/mk2/ember-storybook-demo
プロジェクトのセットアップ
- ※
ember new ...
で新規プロジェクトは生成済み - ※
ember-bootstrap
とember-bootstrap-cp-validations
とember-cp-validations
はインストール済み(例示のために入れてるだけで、必須ではないです)
storybookをセットアップ
npx -p @storybook/cli sb init
ember install @storybook/ember-cli-storybook
ここまでやると、package.jsonにstorybook
というタスクが追加されているので、npm run storybook
で起動するはずです。
...
"scripts":
...
"storybook": "start-storybook -p 6006 -s dist --no-dll",
"build-storybook": "build-storybook -s dist --no-dll"
...
ただし、2020/11/8時点ではnpm run storybook
してもエラーが出て起動しないので、
このissueにあるように、core-js@3.6.5
を依存に追加してあげます。
npm i --save-dev core-js@3.6.5
これで起動するようになります。
開始できるようにする
storybookは起動しているemberからコンポーネントを取得するので、事前にemberを起動しておきます。
ember s
その後、storybookを起動します。
npm run storybook
これで、localhost:6006
でstorybookが動くようになりました。
storyを記述していく
例: ember-bootstrapのフォーム
ember-bootstrapで作成したフォームをstorybook上に出してみたいと思います。ember-cp-validationsでバリデーションも出来るようにしてちゃんと動くかチェックします。
# `-cc`でClassic Componentスタイルのjsファイルを、hbsファイルに加えて生成する
ember g component -cc SampleForm
<BsForm @formLayout="vertical" @model={{this}} @onSubmit={{action "submit"}} as |form|>
<form.element
@controlType="text"
@label="タイトル"
@placeholder="タイトル"
@property="title"
@required={{true}}
/>
<form.element @controlType="textarea" @label="説明" @property="description" />
<form.submitButton>
作成
</form.submitButton>
</BsForm>
import Component from "@ember/component";
import { buildValidations, validator } from "ember-cp-validations";
const Validations = buildValidations({
title: validator("presence", { presence: true, message: "入力が必要です" }),
});
export default Component.extend(Validations, {
title: null,
description: null,
actions: {
submit() {
this.onSubmit(this.title, this.description);
},
},
});
import { action } from "@storybook/addon-actions";
import { hbs } from "ember-cli-htmlbars";
export default {
title: "SampleForm",
argTypes: {},
};
export const Template = () => ({
template: hbs`<SampleForm />`,
context: {
submit: () => action("onSubmit")(),
},
});
これで画面を見ると、動いているのがわかります。
ちゃんとバリデーションも動いているようです。
例: 少しだけ複雑な画面
下図のような、ちょっとだけ複雑そうな画面を作ってみます。(月〜金の時間帯を選んで保存できる、という画面です)
- 9:00~19:00の時間帯はstorybookのcontrols機能で画面上からいじれるようにする
ember g component day-input
# `-gc`でOctane Componentスタイルのjsファイルを、hbsファイルに加えて生成する
ember g component -gc week-input
<BsButtonGroup @value={{@selectedHours}} @type="checkbox" @onChange={{@onChange}} as |bg|>
{{#each @hours as |time|}}
<bg.button @type="info" @outline={{true}} @value={{time}}>
{{time}}
</bg.button>
{{/each}}
</BsButtonGroup>
<div class="row">
<div class="mx-auto col-md-8" style="margin-bottom: 20px">
<div class="row">
<div class="col-md-8">
<BsButton @type="success" @onClick={{@onSave}}>
保存
</BsButton>
<BsButton @type="danger" @onClick={{@onCancel}}>
キャンセル
</BsButton>
</div>
<div class="col-md-4">
<div class="input-group flex-nowrap">
<div class="input-group-prepend">
<span class="input-group-text" id="addon-wrapping">
記入者名
</span>
</div>
<input
type="text"
class="form-control"
placeholder=""
aria-label="表示名"
aria-describedby="addon-wrapping"
value={{@scheduleInput.name}}
{{on "change" this.updateName}}
/>
</div>
</div>
</div>
</div>
</div>
<div class="row" {{did-insert setupHours}}>
<div class="mx-auto col-md-8">
<table>
<tbody>
<tr>
<td>
月
</td>
<td>
<DayInput
@hours={{hours}}
@selectedHours={{@scheduleInput.monHours}}
@onChange={{action (mut this.args.scheduleInput.monHours)}}
/>
</td>
</tr>
<tr>
<td>
火
</td>
<td>
<DayInput
@hours={{hours}}
@selectedHours={{@scheduleInput.tueHours}}
@onChange={{action (mut @scheduleInput.tueHours)}}
/>
</td>
</tr>
<tr>
<td>
水
</td>
<td>
<DayInput
@hours={{hours}}
@selectedHours={{@scheduleInput.wedHours}}
@onChange={{action (mut @scheduleInput.wedHours)}}
/>
</td>
</tr>
<tr>
<td>
木
</td>
<td>
<DayInput
@hours={{hours}}
@selectedHours={{@scheduleInput.thuHours}}
@onChange={{action (mut @scheduleInput.thuHours)}}
/>
</td>
</tr>
<tr>
<td>
金
</td>
<td>
<DayInput
@hours={{hours}}
@selectedHours={{@scheduleInput.friHours}}
@onChange={{action (mut @scheduleInput.friHours)}}
/>
</td>
</tr>
</tbody>
</table>
</div>
</div>
import { action } from "@ember/object";
import Component from "@glimmer/component";
import { tracked } from "@glimmer/tracking";
function generateHours(startHour, endHour) {
return Array.from({ length: endHour - startHour + 1 }, (_, v) => v + startHour).map(
(v) => `${v}:00`
);
}
export default class ScheduleWeekInputComponent extends Component {
@tracked
hours = [];
@action
setupHours() {
const startHour = this.args.schedule.startHour;
const endHour = this.args.schedule.endHour;
this.hours = generateHours(startHour, endHour);
}
@action
updateName(e) {
this.args.scheduleInput.name = e.target.value;
}
}
これで画面のコードは用意できました。storybookのコードを作成します。
import { action } from "@storybook/addon-actions";
import { hbs } from "ember-cli-htmlbars";
export default {
title: "WeekInput",
argTypes: {
startHour: { control: { type: "range", min: 0, max: 24, step: 1 } },
endHour: {
control: { type: "range", min: 0, max: 24, step: 1 },
},
},
};
export const Template = ({ startHour, endHour, ...args }) => ({
template: hbs`
<WeekInput
@schedule={{schedule}}
@scheduleInput={{scheduleInput}}
@onSave={{onSave}}
onCancel={{onCancel}} />
`,
context: {
onSave: () => action("onSave")(),
onCancel: () => action("onCancel")(),
scheduleInput: {
monHours: [],
tueHours: [],
wedHours: [],
thuHours: [],
friHours: [],
satHours: [],
sunHours: [],
},
schedule: {
startHour,
endHour,
},
...args,
},
});
Template.args = {
startHour: 9,
endHour: 19,
};
storybookの画面を見ると、ちゃんと動いているのがわかります。
スライダーをぐりぐりすると良い感じに画面の表示がリアクティブに切り替わるのがわかります。やったね!