【Nuxt.js】props実践編：switchボタンを作ろう！

![switchlast.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/469755/b9044253-cb41-bc03-224d-9d890a2c31eb.gif)
# 前置き

今回はSwitchボタンです！✅
gifカクカクですが
実際はなめらかになります💦
(予告と違う内容ですみません)
inputの真偽値によって
クラスバインディングを行います！

propsを主に使います。
**前回書いた記事と違うのは
Stringを渡すだけではなく
computed関数で切り替えを行う部分です🤗**
propsでのクラスバインディングはこちら
https://note.com/aliz/n/nc4110b1128f0

# 構成

まずはtemplateの構成
inputをlabelで囲うとコードが
スッキリするのでこちらを採用🌱

```bash:書き方①
<label><input type="text"></label>
```


囲わない場合は

```bash:書き方②
<label for="name"></label>
<input type="text" id="name">
```

# Step1: まずはlabel, inputを作成
![switch0.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/469755/aa7dde7e-70f9-1750-2e49-e40de6112a6b.gif)

**◾️label**
・文字列は親で指定したいのでprops
　文字列だけなのでslotでも大丈夫です！
　今回はpropsを複数使うためpropsにします。

**css**
labelの幅を88pxにしたい！
が、labelに指定してしまうと
inputも含んだ長さのためspanを追加します。

**◾️input**
・input type="checkbox"に
　指定可能な属性checkedを使用
　後でクラスバインディングを紐づけるため、
　**まずはcheckedされた状態から作ります✅**

・inputイベント(@input)で$emit
　$emit基礎編はこちら
　https://note.com/aliz/n/nd6e771724cd7

・チェックが付けられるとイベント実行
　基本イベントハンドラは
　まとめてあるここが参考になります！
　https://blog.capilano-fw.com/?p=2787

**css**
まず全体をdivで囲い
そのdivにborderをつけ
●を作るdivを追加します🍩
●はcheckされた時の右側の配置を書きたいので
左側の分もまとめて書いちゃいましょう！✍️




**👩‍💻ここまでのコード**
scssは他のコンポーネントに影響が出ないよう、
基本的に直下セレクタ > をつけています。

```vue:SwitchButton.vue

<template>
 <label class="input switch-button">
   <span class="label">
     {{ label }}
   </span>
   <div class="box">
     <input
       :checked="checked"
       type="checkbox"
       class="input"
       @input="$emit('switch')"
     >
     <div class="mark" />
   </div>
 </label>
</template>

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

<style lang="scss" scoped>
 .switch-button {
   display: flex;
   align-items: center;

   > .label {
     width: 88px;
     min-width: 88px;
     color: red;
   }

   > .box {
     position: relative;
     width: 48px;
     height: 24px;
     border: 2px solid red;
     border-radius: 26px;

     > .mark {
       position: absolute;
       top: 2px;
       left: 2px;
       width: 16px;
       height: 16px;
       background-color: red;
       border-radius: 50%;
       transition: 0.12s;
     }
     
     > .input {
       display: none;

       &:checked ~ .mark {
         transform: translateX(24px);
         background-color: red;
       }
     }
   }
 }
</style>
```


親でpropsのlabelは
直接テキストを渡しています。
:label="変数名"でももちろんOK。
$emitに関しては現時点ではスルーでOK。

```vue:index.vue
<template>
 <div class="page">
   <SwitchButton
     label="LABEL"
     :checked="checked"
   />
 </div>
</template>

<script>
import SwitchButton from '~/components/SwitchButton.vue'

export default {
 components: {
   SwitchButton,
 },
 data () {
   return {
     checked: true,
   }
 }
}
</script>
```

# Step2: クラスバインディングを追加

propsとcomputed関数を使った
クラスバインディングをしていきます。
まずはinputによる真偽値は無視しましょう。


**やりたいこと**
チェックされたかどうかに関係なく
クラスの付け替えで全体の色を変える🎈🧸
なのでこうなればOKです！
![switch2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/469755/b0927227-83c1-8b87-bd98-7b1de3928972.gif)

あとでチェックされていない状態
(●が左にある状態)で
この色に変わるように変更します。

**Point!**
・labelにクラスバインディング
・computed関数で
　propsをswitch関数で切り替え
・親でstatusがinactiveになれば
　inactiveクラスがついてピンクになる
・inactiveクラスがついた時のcssを追加

**👩‍💻ここまでのコード**

```vue:SwitchButton.vue
<template>
 <label
   :class="classes"
   class="input switch-button"
 >
   <span class="label">
     {{ label }}
   </span>
   <div class="box">
     <input
       :checked="checked"
       type="checkbox"
       class="input"
       @input="$emit('switch')"
     >
     <div class="mark" />
   </div>
 </label>
</template>

<script>
export default {
props: {
  status: {
    type: String,
    default: '',
    required: false,
    validator (value) {
      return [
        '',
        'inactive',
      ].includes(value)
    },
  },
  label: {
    type: String,
    default: 'label'
  },
  checked: {
    type: Boolean,
    required: true,
  },
},
computed: {
  classes() {
    switch (this.status) {
      case '':
       return ''
     case 'inactive':
       return 'inactive'
     default:
       return ''
    }
  },
},
}
</script>

<style lang="scss" scoped>
.switch-button {
 display: flex;
 align-items: center;

 > .label {
   width: 69px;
   min-width: 69px;
   color: red;
 }

 > .box {
   position: relative;
   width: 48px;
   height: 24px;
   border: 2px solid red;
   border-radius: 26px;

   > .mark {
     position: absolute;
     top: 2px;
     left: 2px;
     width: 16px;
     height: 16px;
     background-color: red;
     border-radius: 50%;
     transition: 0.12s;
   }

   > .input {
     display: none;

     &:checked ~ .mark {
       transform: translateX(24px);
       background-color: red;
     }
   }
 }

 &.inactive {
   > .label {
     color: pink;
   }

   > .box {
     border: 2px solid pink;

     > .mark {
       background-color: pink;
     }

     > .input:checked ~ .mark {
       background-color: pink;
     }
   }
 }
}
</style>
```

```vue:index.vue
<template>
 <div class="page">
   <SwitchButton
     :checked="checked"
     label="LABEL"
     status="inactive"
   />
 </div>
</template>

<script>
import SwitchButton from '~/components/SwitchButton.vue'

export default {
 components: {
   SwitchButton,
 },
 data () {
  return {
   checked: true,
  }
 },
}
</script>
```

これで準備はOK✨💪

![switchlast.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/469755/3e0927fb-131a-4a60-2ad1-6b80977ce9d9.gif)

# Step3: 真偽値とクラスバインディングの連携

いよいよinputによるクラス付け替えです！
子ではもう全てのpropsを
書いているのでやることはありません。

親で渡したpropsの
・Boolean
・String
これを変数で連携させましょう！

**Point!**
・:checked="value"で
　valueの初期値をfalseに
　最初は選択されていない状態に変更
・statusに三項演算を使うので:を忘れず追加
　valueがtrueなら'', falseなら'inactive'
・@switch="value = !value"で
　switchイベント発火時にtrue, falseの切り替え

つまり初期値チェックしてない状態の時は
inactiveクラスがついて
ピンクになって●が左にある状態🍩
チェックをつけるとinactiveが外れて
通常クラスの赤になって●が右にある状態🍩

**👩‍💻最終コード**

```vue:index.vue
<template>
 <div class="page">
   <SwitchButton
     :checked="value"
     label="LABEL"
     :status="value ? '' : 'inactive'"
     @switch="value = !value"
   />
 </div>
</template>

<script>
import SwitchButton from '~/components/SwitchButton.vue'

export default {
 components: {
   SwitchButton,
 },
 data () {
   return {
     value: false,
   }
 }
}
</script>
```


```vue:SwitchButton.vue
<template>
 <label
   :class="classes"
   class="input switch-button"
 >
   <span class="label">
     {{ label }}
   </span>
   <div class="box">
     <input
       :checked="checked"
       type="checkbox"
       class="input"
       @input="$emit('switch')"
     >
     <div class="mark" />
   </div>
 </label>
</template>

<script>
export default {
props: {
  status: {
    type: String,
    default: '',
    required: false,
    validator (value) {
      return [
        '',
        'inactive',
      ].includes(value)
    },
  },
  label: {
    type: String,
    default: 'label'
  },
  checked: {
    type: Boolean,
    required: true,
  },
},
computed: {
  classes() {
    switch (this.status) {
      case '':
       return ''
     case 'inactive':
       return 'inactive'
     default:
       return ''
    }
  },
},
}
</script>

<style lang="scss" scoped>
.switch-button {
 display: flex;
 align-items: center;

 > .label {
   width: 69px;
   min-width: 69px;
   color: red;
 }

 > .box {
   position: relative;
   width: 48px;
   height: 24px;
   border: 2px solid red;
   border-radius: 26px;

   > .mark {
     position: absolute;
     top: 2px;
     left: 2px;
     width: 16px;
     height: 16px;
     background-color: red;
     border-radius: 50%;
     transition: 0.12s;
   }

   > .input {
     display: none;

     &:checked ~ .mark {
       transform: translateX(24px);
       background-color: red;
     }
   }
 }

 &.inactive {
   > .label {
     color: pink;
   }

   > .box {
     border: 2px solid pink;

     > .mark {
       background-color: pink;
     }

     > .input:checked ~ .mark {
       background-color: pink;
     }
   }
 }
}
</style>
```

次回はVuexの続きをやります！
公開予定日は2/28(金)です。

**記事が公開したときにわかる様に、
note・Twitterフォローをお願いします😀**
https://twitter.com/aLizlab