Vue.js 2.0 Tabs (inline)

Vue.js 2.0 Tabs

by Michael Menaker

HTML

<div id="app">
  <tabs>
    <tab title="A">
      <p>Content A</p>
    </tab>
    <tab title="B">
      <div>
        <p>Content B</p>
      </div>
    </tab>
    <tab title="C" v-bind:is-active="true">
      <p>Content C</p>
    </tab>
  </tabs>
</div>

<template id="tabs">
  <div class="tabs">
    <ul>
      <li v-for="tab in tabs" v-bind:class="{'active': current === tab}" @click="changeTab(tab)">
        {{tab.title}}
      </li>
    </ul>
    <slot></slot>
  </div>
</template>
<template id="tab">
  <transition :name="slideType" @after-leave="afterLeave">
    <div v-show="active" :class="{'active': active}">
      <slot></slot>
    </div>
  </transition>
</template>

CSS

.tabs > ul {
  overflow: hidden;
  font: bold 10px Verdana, sans-serif;
  margin: 0;
  padding: 0;
  padding-bottom: 10px;
}

.tabs > ul li {
  float: left;
  list-style: none;
  background: #ddd;
  border: 1px solid #f5f7fa;
  color: #666;
  cursor: pointer;
  display: block;
  padding: 0 30px;
  text-decoration: none;
  border-top: 1px solid #ddd;
}

.tabs > ul li:hover {
  background: #eee;
  text-decoration: none;
}

.tabs > ul li.active {
  color: #333;
  background: #f5f7fa;
}

.slide-left-leave-active,
.slide-left-enter-active,
.slide-right-leave-active,
.slide-right-enter-active {
  transition: .5s;
}

.slide-left-enter {
  transform: translate(20%, 0);
}

.slide-left-leave-to {
  transform: translate(-20%, 0);
}

.slide-right-enter {
  transform: translate(-20%, 0);
}

.slide-right-leave-to {
  transform: translate(20%, 0);
}

JavaScript

Vue.component('tabs', {
  template: '#tabs',
  data: function() {
    return {
      tabs: [],
      current: null
    };
  },
  methods: {
    addTab: function(tab) {
      this.tabs.push(tab);
      if (tab.active === true) {
        this.current = tab;
      }
    },
    changeTab(tab) {
      const slideType = this.getSlideType(tab);

      this.deactivateTab(slideType);
      this.setCurrent(tab, slideType);
    },
    deactivateTab(slideType) {
      this.current.slideType = slideType;
      this.current.active = false;
    },
    setCurrent(tab, slideType) {
      this.current = tab;
      this.current.slideType = slideType;
    },
    activateTab() {
      this.current.active = true;
    },
    getSlideType(tab) {
      const newTabIndex = this.tabs.indexOf(tab);
      const oldTabIndex = this.tabs.indexOf(this.current);
      return newTabIndex > oldTabIndex ? "slide-left" : "slide-right";
    }
  }
});
Vue.component('tab', {
  template: '#tab',
  data: function() {
    return {
      active: false,
      slideType: "slide-left"
    };
  },
  props: {
    'title': {
      required: true,
      type: String
    },
    'isActive': {
      required: false,
      type: Boolean,
      default: false
    }
  },
  created: function() {
    this.active = this.isActive;
  },
  mounted: function() {
    this.$parent.addTab(this);
  },
  methods: {
    afterLeave() {
      this.$parent.activateTab();
    }
  }
});
var app = new Vue({
  el: '#app',
  data: {}
});