Vue

by joplomacedo

HTML

<link href="https://fonts.googleapis.com/css?family=Fira+Sans:200,300,400,500,600,700,800,900" rel="stylesheet">
      <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.3.1/css/all.css" integrity="sha384-mzrmE5qonljUremFsqc01SB46JvROS7bZs3IO2EmfFsd15uHvIt+Y8vEf7N7fWAU" crossorigin="anonymous">


      <div id="app">
        <div class="page_header">
          <div class="page_header__logo_block">
            <img class="page_header__logo" src="https://static.holdonsam.com/site/2_0/img/reoodle_logos/white.svg" alt="">
            <div class="page_header__logo_block_divider"></div>
            <div class="page_header__pricing_page_label">Pagamentos</div>
          </div>
          <div class="page_header__nav">
            <div class="page_header__nav_link">
              Back to App
            </div>
          </div>
        </div>

        <div class="page_body">
          <div class="tabs__nav">
            <div @click="() => changeView('plans')" class="tabs__nav_link" :class="currentView === 'plans'? 'is-selected' : null">Planos</div>
            <div @click="() => changeView('receipts')" class="tabs__nav_link" :class="currentView === 'receipts'? 'is-selected' : null">Recibos</div>
          </div>

          <div class="tabs__body">
            <div v-if="currentView === 'plans'">

              <div>
                <p class="t-h--3">
                  Plano corrente
                </p>

                <div class="active_plan_card" v-if="currentPlan !== null">
                  <div>
                    <div class="t-label">Plano</div>
                    <div>{{currentPlan.name}}</div>
                  </div>

                  <div>
                    <div class="t-label">Data de compra</div>
                    <div>{{purchaseDate}}</div>
                  </div>

                  <div>
                    <div class="t-label">Data de renovação</div>
                    <div>{{renewalDate}}</div>
                  </div>

         ...

CSS

*,
*:after,
*:before {
  -webkit-box-sizing: border-box;
  -moz-box-sizing: border-box;
  box-sizing: border-box;
  font: inherit;
  border: 0;
  padding: 0;
  margin: 0;
  color: inherit;
  text-decoration: none;
}


html {
  font-size: 100%;
  font-family: helveticaneue, helvetica, arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -webkit-text-size-adjust: 100%;
  -ms-text-size-adjust: 100%;
}

html {
  line-height: 1.7;
  font-size: 17px;
  height: 100%;
  background: #fffaf3;
  color: hsla(259, 13%, 49%, 1);
  font-weight: 400;
  font-family: fira sans, sans-serif;
  --color-brand: #5e2bcc;
}

body {
  background: transparent;
}


.t-h--2 {
  font-weight: 600;
  color: var(--color-brand);
  font-size: 1.25em;
}

.t-h--3 {
  font-weight: 800;
  color: var(--color-brand);
  font-size: 1.25em;
  line-height: 1.2;
  margin-bottom: .6em;
}


.app-btn {}

.app-btn_group {
  display: flex;
}

.app-btn_group .app-btn:first-child {
  border-top-right-radius: 0;
  border-bottom-right-radius: 0;
}

.app-btn_group .app-btn:last-child {
  border-top-left-radius: 0;
  border-bottom-left-radius: 0;
}

.app-radio {
  display: inline-flex;
  align-items: center;
  margin-right: 1em;
  cursor: pointer;
  --transition: .12s;
}

.app-radio__label {}

.app-radio__input {
  display: none;
}

.app-radio__box {
  position: relative;
  display: inline-flex;
  height: 1.1em;
  width: 1.1em;
  border-radius: 50px;
  border: 1px solid grey;
  margin-right: .3em;
  align-items: center;
  justify-content: center;
  transition: var(--transition);
}

.app-radio__check {
  position: absolute;
  height: .5em;
  width: .5em;
  border-radius: 50px;
  background-color: var(--color-brand);

  opacity: 0;
  transform: scale(0);
  transition: var(--transition);
}

.app-radio__input:checked~.app-radio__box {
  border-color: var(--color-brand);
}

.app-radio__input:checked~.app-radio__box>.app-radio__check {
  opacity: 1;
  transform: scale(1);
}

.app-divider {
  height: 1px;
 ...

Vue

Vue.component('app-divider', {
	template: `
    <div class="app-divider"></div>
   `
});

Vue.component('app-radio', {
	template: `
    <label class="app-radio">
      <input
        type="radio"
        class="app-radio__input"
        :value="value"
        :checked="isChecked"
        @change="$emit('change', value)"
      />
      
      <span class="app-radio__box">
      	<span class="app-radio__check"></span>
      </span>
      
      <span class="app-radio__label">
      	{{label}}
      </span>
    </label>
  `,
  
  model: {
    prop: 'modelValue',
    event: 'change'
  },
  
  props: {
  	label:{
    	type: String,
      required: true
    },
    
		value: {
    	type: [String, Number],
      required: true
    },
    
    modelValue: {
    	default: ""
    }
  },
  
  computed: {
  	isChecked() {
			return this.modelValue == this.value
  	}
  }
})


Vue.component('app-btn-group', {
	template: `
  <div class="app-btn_group">
  	<slot />
  </div>
  `,
})

Vue.component('app-btn', {
	template: `
  <component :is="tag" class="app-btn">
  	<span class="app-btn__content">
    	<span class="app-btn__icon" :class="icon" v-if="icon"></span>
    	<span class="app-btn__text">
		  	<slot />
      </span>
    </span>
  </component>
  `,
  props: {
		tag: {
	    default: 'button'
    },
    icon: {
    	default: null
    }
  }
})



new Vue({
  el: "#app",

  data: {
    plans: [
      { id:"basic_monthly", name: 'Básico', monthlySms: 200, price: 19, billingInterval: 1},
      { id:"basic_yearly", name: 'Básico', monthlySms: 200, price: 14, billingInterval: 12},
      
      { id:"standard_monthly", name: 'Standard', monthlySms: 400, price: 39, billingInterval: 1},
      { id:"standard_yearly", name: 'Standard', monthlySms: 400, price: 29, billingInterval: 12},
            
      { id:"pro_monthly", name: 'Pro', monthlySms: 800, price: 69, billingInterval: 1},
      { id:"pro_yearly", name: 'Pro', monthlySms: 800, price: 59, billingInterval: 12},
    ].map( plan...