forestec

勉強した内容をつらつらと備忘録として記していきます。

Atomic Design について考える 〜Buttonコンポーネント編〜

かなり久々の更新。

最近仕事ではc#をやりつつフロントもちょこちょこ触っています。
現在使用しているのはjQueryですが、
いい加減フロント改善したいという機運が社内でも高まってきていて 、
次はVueかReactかみたいな感じになっています。

なので自分も焦ってVue.jsとAtomic Design について勉強しているところです。
その過程で得た自分の理解というか整理を残しておきたくてこの記事を書きます。

開発環境はNuxt.jsなのでその前提で書いてはいますが本題はあくまでAtomic Designについてです。

Buttonコンポーネントについての記事なんて世の中にありふれてるじゃねーかという感じではありますが、
Button一つとっても考えることはたくさんあったので気にせず書いていきます。

ButtonはAtomsかMoleculesか

まずはAtomic DesignにおいてButtonをどこに分類するかです。

先に言ってしまうと自分はどちらでも間違っていないと思っています。
それは何も考えずにどっちに作ってもいいよって意味ではなく、
デザイン定義に沿って決めるべきだと考えているからです。

例えば以下のようなボタンを作成したいとなった場合

f:id:forest_yuzuremon:20190525155930p:plain     f:id:forest_yuzuremon:20190525160144p:plain

1つ目はベーシックなテキストだけ入ったButton
2つ目はIconが入ったButton

前者のButtonであれば迷わずAtomsかなと自分は思います。

ですが後者のButtonだとどうでしょう?
IconはAtomsとして定義しておくべきな気がします。
それが中に含まれているButtonをAtomsとするべきでしょうか?

ButtonをAtomsとした場合、
Buttonコンポーネント内でIconコンポーネントを呼び出すことになってしまいます。
Atomsには最小の単位まで分解したものを定義するはずなのでこれだと違和感を感じます。
ではこの場合はIconコンポーネントをAtoms、
ButtonコンポーネントをMoleculesとして定義すればどうでしょう。
そうすればButtonコンポーネントからIconコンポーネントを呼び出しても違和感は無くなりました。

これで解決したかと思いきや1つ問題が発生しています。
なぜなら後者のButtonをMoleculesにした場合、
前者のButtonはどうすればいいのかとなってしまいます。
前者のButtonをAtomsに定義しても良いですが、
AtomsにもMoleculesにもButtonが存在しているのが少し気持ち悪いです。
かといって前者のButtonもMoleculesに入れても違和感があります...

といった感じで自分も色々悩んだのですが次のような方法で解決しました。

色々言ったがButtonはやっぱりAtomsにする

サブタイの通りButtonはAtomsにしました。
IconもAtomsにしました。
そして1つのButtonコンポーネントでIconが付く場合も付かない場合も表現出来るようにします。

自分は以下のようなButtonコンポーネントを作成しました。

<template lang="pug">
button.btn
slot(name='leftIcon')
span.btn-label {{ label }}
slot(name='rightIcon')
</template>

vue.jsのslot機能を使っているので、
Buttonコンポーネントを使用する側の親コンポーネントが 、
slot部分に対してIconコンポーネントを挿入した場合はIcon付きのButton、
挿入しない場合はテキストのみのButtonを表示できるようになっています。
さらにrightIconも定義して右側にIconを挿入することも出来るようになっています。

これでIconコンポーネントはもちろんButton以外でも再利用可能ですし、
Buttonコンポーネントも1つのコンポーネントで様々なButtonを表現出来るようになりました。

 

もっとButtonコンポーネントについて考える

Icon問題については上の実装で解決できましたが、
Buttonコンポーネントについてはまだまだ考えないといけないことがあります。
なので順番に整理していきましょう。

 

Buttonの色を変えたい

以下のように先ほど定義したようなベーシックな見た目のButtonだけでなく、
赤色のButtonも定義したいとなったとしたらどうすればいいでしょうか?

f:id:forest_yuzuremon:20190525155930p:plain     f:id:forest_yuzuremon:20190525171944p:plain

新しくRedButtonコンポーネントを作成などしているとコンポーネントが大量に出来てしまいます。

なのでButton.vueを変更して動的に見た目を変更出来るようします。

 

<template lang="pug">
button.btn(:class="theme")
slot(name='leftIcon')
span.btn-label {{ label }}
slot(name='rightIcon')
</template>

<script>
export default {
props: {
label: {
type: String,
required: false,
default: ''
},
theme: {
type: String,
required: true
}
}
}
</script>

<style lang="scss" scoped>
.btn {
display: flex;
align-items: center;
justify-content: center;
border-radius: 5px;

&.white {
background-color: #FFFFFF;
border: 1px solid #E0E0E0;
color: #262322;
}

&.red {
background-color: #CB4248;
color: #FFFFFF;
}
}
</style>

 

Buttonコンポーネントを使用する際にpropstheme変数に何が指定されているかでclassを動的に変更出来るようになっています。

themewhiteを指定

f:id:forest_yuzuremon:20190525155930p:plain

themeredを指定

f:id:forest_yuzuremon:20190525171944p:plain

これでButtonコンポーネントがさらに汎用的に使えるようになってきました。

ですがこれではまだ問題があります。
もしRed Buttonを使っていたけど色を変更して、
全てBlue Buttonに変更したいとなった場合はどうすればいいでしょうか?

Buttonコンポーネント内にBlue Buttonの定義を作成して、
プロジェクト内のthemeredを指定している箇所を全てリファクタする!
気が遠くなりそうです...

これは色に対して依存をしているのが問題だと思うので、
自分はボタンの持つ役割に対して依存したclass名を使うようにしています。

デザインを定義する際にButtonのスタイルだけでなく、
それぞれのスタイルのButtonが持つ役割も定義されているのではないかと思います。
その役割に対してclass名を依存させておけばcssの変更だけで済むようになるのではないでしょうか。

ということでclass名を変更します。

 

<template lang="pug">
button.btn(:class="theme")
slot(name='leftIcon')
span.btn-label {{ label }}
slot(name='rightIcon')
</template>

<script>
export default {
props: {
label: {
type: String,
required: false,
default: ''
},
theme: {
type: String,
required: true
}
}
}
</script>

<style lang="scss" scoped>
.btn {
display: flex;
align-items: center;
justify-content: center;
border-radius: 5px;

&.cancel {
background-color: #FFFFFF;
border: 1px solid #E0E0E0;
color: #262322;
}

&.submit {
background-color: #CB4248;
color: #FFFFFF;
}
}
</style>

今回はsubmitcancelという2つを設定してみました。


Buttonを無効化させたい

次はButtonを無効化させたい場合はどうすればいいかについて考えてみます。
これは基本的にはさっきと同じでv-bindを使えば解決すると思います。

<template lang="pug">
button.btn(:class="theme")(:disabled="disabled")
slot(name='leftIcon')
span.btn-label {{ label }}
slot(name='rightIcon')
</template>

<script>
export default {
props: {
label: {
type: String,
required: false,
default: ''
},
theme: {
type: String,
required: true
},
disabled: {
type: Boolean,
required: false,
default: false
}
}
}
</script>

<style lang="scss" scoped>
.btn {
display: flex;
align-items: center;
justify-content: center;
border-radius: 5px;

&.cancel {
background-color: #FFFFFF;
border: 1px solid #E0E0E0;
color: #262322;
}

&.submit {
background-color: #CB4248;
color: #FFFFFF;
}

&:disabled {
background-color: #E5E5E5;
border: 1px solid #CBCFCC;
color: #CBCFCC;
}
}
</style>

propsdisabled変数をBooleanで定義して値が入ってくればButtonをdisabledにしています。
無効化したくない時はそもそも変数を指定しなくても問題なくしてあります。


Buttonのサイズを変更したい

最後はButtonのサイズについて考えてみたいと思います。

<template lang="pug">
button.btn(:class="theme")(:disabled="disabled")(:style="{width: width, height: height}")
slot(name='leftIcon')
span.btn-label {{ label }}
slot(name='rightIcon')
</template>

横幅と高さを変数で受け取ることでサイズを動的に変更出来るようにしました。

ですがこれもあまりいい方法だとは思えません。
Atomic Designの利点としてデザインの統一化を行えることが挙げられると思いますが、
上記の方法だと統一化は行えるでしょうか?
指定の自由度が高すぎてサイズがバラバラのButtonがたくさん出来てしまいかねません。
なので自分はButtonの色を変更する際と同じ方法を用いています。
Buttonのサイズについてもあらかじめデザインが定義されているものではないかと思うので。

<template lang="pug">
button.btn(:class="[theme, size]")(:disabled="disabled")
slot(name='leftIcon')
span.btn-label {{ label }}
slot(name='rightIcon')
</template>

<script>
export default {
props: {
label: {
type: String,
required: false,
default: ''
},
theme: {
type: String,
required: true
},
disabled: {
type: Boolean,
required: false,
default: false
},
size: {
type: String,
required: false,
default: 'large'
}
}
}
</script>

<style lang="scss" scoped>
.btn {
display: flex;
align-items: center;
justify-content: center;
border-radius: 5px;

&.cancel {
background-color: #FFFFFF;
border: 1px solid #E0E0E0;
color: #262322;
}

&.submit {
background-color: #CB4248;
color: #FFFFFF;
}

&:disabled {
background-color: #E5E5E5;
border: 1px solid #CBCFCC;
color: #CBCFCC;
}

&.large {
width: 210px;
height: 42px;
}

&.medium {
width: 120px;
height: 35px;
}

&.small {
width: 75px;
height: 30px;
}
}
</style>

large, medium, smallの3種類のサイズを定義してみました。
これでButtonのサイズも統一化出来ると思います。

 

設定されたButton一覧

作成したButtonコンポーネント、Iconコンポーネントで作成されたButtonの一覧はこんな感じになりました。

f:id:forest_yuzuremon:20190526000547p:plain

ButtonコンポーネントとIconコンポーネントだけで様々なButtonを作ることが出来ました。

 

最後に

思った以上に長くなってしまいましたが、
今回この記事に書いたことはあくまで自分なりの理解なので、
ここは間違ってるとかこうした方がいいとかもしくは自分はこんな整理でやってるとかコメントいただけると嬉しいです。

Button以外のコンポーネントについても考えがまとまったら記事を書いてみようと思います。