準備
必要なコマンドをインストール
npm i -g create-react-app @storybook/cli
version は以下の通りになっていればOK
create-react-app --version
1.4.0
getstorybook --version
3.2.3
練習用のプロジェクトディレクトリを作成して、storybook を起動します
create-react-app practice-storybook
cd practice-storybook
getstorybook
npm run storybook
browser で動作確認をしましょう。
練習1: button を作成する
src/stories/Button.js
が既に存在していますがこの Button とは別に独自の Button を作成します。
mkdir src/components
touch src/components/Button.js
create src/components/Button.js
import React from 'react'
const styles = {
button: {
borderRadius: "3px",
backgroundColor: "#3498db",
padding: "7px 16px",
width: "100px",
fontFamily: "YuGo-Bold",
fontSize: "12px",
fontWeight: "normal",
fontStyle: "normal",
fontStretch: "normal",
lineHeight: "12px",
letterSpacing: "normal",
textAlign: "center",
color: "#fff",
}
}
const Button = ({ children, onClick }) => (
<div style={styles.button} onClick={onClick}>
{children}
</div>
)
export default Button
上記の src/components/Button.js
を src/stories/Button.js
で warp してカタログ化します。
getstorybook
を実行した時に既に生成されているファイルがありますがそれを編集します。
edit stories/index.js
import React from 'react';
import { storiesOf } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import Button from '../src/components/Button'
storiesOf('Button', module)
.add('with text', () => <Button onClick={action('clicked')}>Hello Button</Button>)
.add('with some emoji', () => <Button onClick={action('clicked')}>😀) 😎😀 👍😎 💯👍</Button>)
この状態で一度動作確認をします。
練習2: 擬似クラスへの対応方法
mouseOver した時に色を少し変えたい場合、mouseEnter, mouseOut などでは頑張りたくないと思います。
<div style={{property: values}}>..</div>
のように sytle に記述した場合、:hover
のような擬似クラスを記述できない(勘違いだったらすみません)ので aphrodite
というモジュールを使用します。
npm install --save aphrodite
edit src/components/Button.js
import React from 'react'
+import { StyleSheet, css } from 'aphrodite'
-const styles = {
+const styles = StyleSheet.create({
button: {
borderRadius: "3px",
backgroundColor: "#3498db",
@@ -16,10 +17,10 @@ const styles = {
textAlign: "center",
color: "#fff",
}
-}
+})
const Button = ({ children, onClick }) => (
- <div style={styles.button} onClick={onClick}>
+ <div className={css(styles.button)} onClick={onClick}>
{children}
</div>
)
const styles = StyleSheet.create({...})
を実行すると HTML に以下の style 要素が追加されます
<style type="text/css" data-aphrodite="">.button_1w5cv5t{font-weight:normal !important;border-radius:3px !important;padding:17px 0 !important;max-width:300px !important;font-family:YuGo-Bold !important;font-size:16px !important;background-color:#3498db !important;font-style:normal !important;font-stretch:normal !important;line-height:16px !important;letter-spacing:normal !important;text-align:center !important;color:#fff !important;}</style>
css(styles.button)
を実行すると以下の class名を取得します
button_1w5cv5t
この仕組みを利用して :hover
擬似クラスを使います。
edit src/components/Button.js
letterSpacing: "normal",
textAlign: "center",
color: "#fff",
+ ':hover': {
+ backgroundColor: "#3182B8"
+ }
}
練習3: 継承する
scss
などで @extend
したい場合があると思います。
Javascript で同じ事をする場合色々な方法があると思いますがここでは以下のように適用させたいスタイルを配列で css
関数に渡す方法を紹介します。
const styles = StyleSheet.create({
fontFamily = {
fontFamily: "YuGo-Bold",
fontSize: "12px",
fontWeight: "normal",
fontStyle: "normal",
fontStretch: "normal",
},
button = {
borderRadius: "3px",
fontSize: "12px",
backgroundColor: "#3498db",
padding: "7px 16px",
width: "100px",
lineHeight: "12px",
letterSpacing: "normal",
textAlign: "center",
color: "#fff",
':hover': {
backgroundColor: "#3182B8"
}
}
})
className={css([styles.fontFamily, styles.button])}
配列順にスタイルを読み込みます。同じスタイルを読み込んだ場合は後から読んだスタイルで上書きされます。
練習4: modal を作成する
component の再利用を行います。自分以外の優れたエンジニアがすでにコンポーネントを用意してくれています。
いろんな人が modal component を作ってくれていると思いますが今回は https://github.com/yuanyan/boron
を使う方法を紹介します。
npm install --save boron
touch src/components/Modal.js src/stories/Modal.js
最初にシンプルな Modal Component を作成します。
create src/components/Modal.js
import React, { Component } from 'react'
import { StyleSheet, css } from 'aphrodite'
import FadeModal from 'boron/FadeModal'
class Modal extends Component {
showModal() {
this.refs.modal.show()
}
hideModal() {
this.refs.modal.hide()
}
render() {
return (
<div>
<FadeModal ref="modal">
<h2>I am a dialog</h2>
<button onClick={this.hideModal.bind(this)}>Close</button>
</FadeModal>
</div>
)
}
}
export default Modal
次に先ほど作成した Modal のカタログとなる Component を作成します。
create src/stories/Modal.js
import React, { Component } from 'react'
import Modal from '../components/Modal'
class ModalStory extends Component {
showModal() {
this.refs.modal.showModal()
}
render() {
return (
<div>
<button onClick={this.showModal.bind(this)}>Open</button>
<Modal ref="modal" />
</div>
)
}
}
export default ModalStory
最後に src/stories/index.js
を修正します
import { storiesOf, action, linkTo } from '@kadira/storybook'
import Button from './Button'
import Welcome from './Welcome'
+import Modal from './Modal'
storiesOf('Welcome', module)
.add('to Storybook', () => (
@@ -15,3 +16,8 @@ storiesOf('Button', module)
.add('with some emoji', () => (
<Button onClick={action('clicked')}>😀 😎 👍 💯</Button>
));
+
+storiesOf('Modal', module)
+ .add('my modal', () => (
+ <Modal />
+ ))
この状態で動作確認をします。
練習5: modal を装飾する
svg なアイコンを install します
npm install --save react-icons
edit src/components/Modal.js
import React, { Component } from 'react'
import { StyleSheet, css } from 'aphrodite'
import FadeModal from 'boron/FadeModal'
import MdClose from 'react-icons/lib/md/close'
import Button from './Button'
const styles = StyleSheet.create({
modal: {
borderRadius: "4px",
border: "solid 1px #dddddd",
},
header: {
padding: "17px 12px",
fontFamily: "YuGo-Bold",
fontSize: "15px",
fontWeight: "normal",
fontStyle: "normal",
fontStretch: "normal",
lineHeight: "15px",
letterSpacing: "normal",
backgroundColor: "#fafafa",
borderBottom: "solid 1px #dddddd",
color: "#333",
},
icon: {
float: "right",
width: "15px",
height: "15px",
cursor: "pointer",
},
body: {
padding: "30px",
fontFamily: "YuGo",
fontSize: "14px",
fontWeight: "normal",
fontStyle: "normal",
fontStretch: "normal",
lineHeight: "14px",
letterSpacing: "normal",
backgroundColor: "#fff",
borderBottom: "solid 1px #dddddd",
color: "#333",
},
footer: {
textAlign: "right",
padding: "30px",
backgroundColor: "#fff",
},
buttons: {
margin: 0,
padding: 0,
},
button: {
display: "inline-block",
marginRight: "20px",
":last-child": {
marginRight: 0
}
}
})
class Modal extends Component {
showModal() {
this.refs.modal.show()
}
hideModal() {
this.refs.modal.hide()
}
render() {
return (
<FadeModal contentStyle={{backgroundColor: "none"}} ref="modal">
<div className={css(styles.modal)}>
<div className={css(styles.header)}>
header xxxxxxxxx
<MdClose className={css(styles.icon)} onClick={this.hideModal.bind(this)} />
</div>
<div className={css(styles.body)}>
body xxxxxxxxxxxxxxx
</div>
<div className={css(styles.footer)}>
<ul className={css(styles.buttons)}>
<li className={css(styles.button)}>
<Button onClick={this.hideModal.bind(this)}>Close</Button>
</li>
<li className={css(styles.button)}>
<Button onClick={this.hideModal.bind(this)}>Submit</Button>
</li>
</ul>
</div>
</div>
</FadeModal>
)
}
}
export default Modal
storybook