DEV Community

Cover image for Let’s code a dribble design with Vue.js & Tailwindcss (Working demo) — Part 2 of 2
Fayaz Ahmed
Fayaz Ahmed

Posted on • Originally published at Medium on

5 2

Let’s code a dribble design with Vue.js & Tailwindcss (Working demo) — Part 2 of 2

Let’s code a dribble design with Vue.js & Tailwindcss (Working demo) — Part 2 of 2

Article #6 of my “1 article a day till lockdown ends”

So in the part 1 of this article, we made a UI from the dribble design we picked and used tailwindcss to code it. Let’s add the behaviour & some code to actually make it work.

We have divided our input fields into seperate components and trying to get their value by clicking a button which is outside these components, due to which we need some logic to hear changes from these components to our parent component, i.e our index.vue file, it needs to listen to changes happening inside gender.vue.

Vue let’s you listen to child components using the emit property. So we need to “emit” a function in our child component and a listener in our parent component.

Child and parent component communication

In the above image, a child component has a button and we want to pass a value to our parent component, I will add a custom event listener in our parent component —  the child component will be emitting a function like , the name-listener is the key here and vue will listen to it whenever emitted.

Let’s do the same to our gender.vue file, where we will change the value on clicking the male/female card and emit the value to our index.vue file.

Here is how I did it.

<template>
<section class="grid grid-cols-2 gap-2 mb-6">
<div
class="rounded-md bg-gray-800 p-4 w-full transition-all duration-300 cursor-pointer"
@click="toggle('male')"
:class="gender == 'male' ? null : 'opacity-25 shadow-md'"
>
<svg
class="w-16 h-16 mx-auto"
fill="#fff"
stroke="#fff"
viewBox="0 0 384 384"
>
<path
d="M383.793 13.938c-.176-1.38-.48-2.708-.984-3.954-.016-.03-.016-.074-.024-.113 0-.008-.008-.016-.015-.023-.555-1.313-1.313-2.504-2.168-3.61-.211-.261-.418-.52-.641-.765-.914-1.032-1.906-1.985-3.059-2.762-.03-.024-.07-.031-.101-.055-1.114-.734-2.344-1.289-3.633-1.726-.32-.114-.633-.211-.961-.297C370.855.266 369.465 0 368 0H256c-8.832 0-16 7.168-16 16s7.168 16 16 16h73.367l-95.496 95.496C208.406 107.13 177.055 96 144 96 64.602 96 0 160.602 0 240s64.602 144 144 144 144-64.602 144-144c0-33.04-11.121-64.383-31.504-89.871L352 54.625V128c0 8.832 7.168 16 16 16s16-7.168 16-16V16c0-.336-.078-.656-.098-.984a16.243 16.243 0 00-.109-1.079zM144 352c-61.762 0-112-50.238-112-112s50.238-112 112-112c29.902 0 58.055 11.64 79.223 32.734C244.359 181.945 256 210.098 256 240c0 61.762-50.238 112-112 112zm0 0"
/>
</svg>
<p class="text-center mt-8 uppercase font-bold">male</p>
</div>
<div
class="rounded-md bg-gray-800 p-4 w-full transition-all duration-300 cursor-pointer"
@click="toggle('female')"
:class="gender == 'female' ? null : 'opacity-25 shadow-md'"
>
<svg
class="w-16 h-16 mx-auto"
fill="#fff"
stroke="#fff"
viewBox="-56 0 384 384"
>
<path
d="M272 136C272 61.008 210.992 0 136 0S0 61.008 0 136c0 69.566 52.535 127.016 120 134.977V304H88c-8.832 0-16 7.168-16 16s7.168 16 16 16h32v32c0 8.832 7.168 16 16 16s16-7.168 16-16v-32h32c8.832 0 16-7.168 16-16s-7.168-16-16-16h-32v-33.023c67.465-7.961 120-65.41 120-134.977zm-240 0C32 78.656 78.656 32 136 32s104 46.656 104 104-46.656 104-104 104S32 193.344 32 136zm0 0"
/>
</svg>
<p class="text-center mt-8 uppercase font-bold">female</p>
</div>
</section>
</template>
<script>
export default {
data() {
return {
gender: "male"
};
},
methods: {
toggle(val) {
this.gender = val;
this.$emit("genderListener", val);
}
}
};
</script>
view raw gender.vue hosted with ❤ by GitHub

For the Height component I will use a watch proprty of vue, since the slider event is not explicitly triggering a manual event on value change, we will add a watch listerner and emit the value there.

<template>
<section class="rounded-md shadow-md bg-gray-800 p-4 text-center mb-6">
<p class="font-bold">HEIGHT</p>
<p class="text-5xl font-bold">
{{ height }}<small class="text-sm">cm</small>
</p>
<input
type="range"
min="120"
max="215"
v-model="height"
class="slider w-full h-1 rounded-lg outline-none"
/>
</section>
</template>
<script>
export default {
data() {
return {
height: 120
};
},
watch: {
height: function() {
this.$emit("heightListener", parseInt(this.height));
}
}
};
</script>
view raw height.vue hosted with ❤ by GitHub

Similarly add emit events for our age and weight component. I have added a long press directive plugin to the weight and age buttons, which let’s you update the value when you hold the buttons.

Calculating the BMI.

Now, that we have received all our value in our parent component. To calculate the BMI, the formula is weight(kg)/height*height(m) , and we also find out that age and gender are not needed to calculate the BMI 😂.

I would suggest you add some sort of validation before showing the results, like handle the negative values and stuff.

Lets show the BMI in the result Page.

There are multiple ways we can pass the bmi to the next , we could use vuex, and store the value there, use localStorage or we could just pass the bmi value in the url, because the other two methods seem like a overkill. The below function is calculating the BMI and passing the value as a parameter in the url and redirecting to the result page.

calculateBMI() {
var met = this.height / 100;
const bmi = this.weight / (met * met);
this.$router.push({
query: { bmi: bmi.toFixed(2) },
path: "/result"
});
}
view raw calculate.js hosted with ❤ by GitHub

We can capture the bmi from the URL by using the route object of vue like $route.query.bmi . We now have the value, all we need to do is just show it in our result page, this was the design from the dribble page.

There’s also the BMI range classification, which I found in Wikipedia. Let’s make use of this as well.

There’s a Re calculate button, lets just redirect them back to home and for the “Save Button” Let’s replace it with “Share” chrome’s Web Share API.

Here is the boiler plate code you can use to build the UI

<template>
<div class="container mx-auto pt-6 px-4 max-w-lg">
<p class="text-4xl font-bold mb-4">Your Result</p>
<div class="rounded bg-gray-700 p-6 text-center">
<p class="text-green-400 font-bold uppercase">Normal</p>
<p class="text-6xl font-bold text">{{ $route.query.bmi }}</p>
<p class="text-gray-400 font-bold">Normal BMI Range</p>
<p>18.5 - 24.9</p>
<p>You have a normal body weight. Good Job!</p>
<button class="my-4 bg-gray-600 p-4 rounded w-full">SHARE</button>
</div>
<button
class="bg-superpink font-bold uppercase p-4 fixed w-full bottom-0 left-0"
>
RE Calculate BMI
</button>
</div>
</template>
<script>
export default {};
</script>
view raw result.vue hosted with ❤ by GitHub

The Final result page will look this, I have also added a Webshare button which share’s your BMI with others, this works only on phones though.

So far, we have divided a design in to components, and made the UI, added functionality with vue and passed the value in the next page.

This concludes this small project hope you enjoyed.

You can find the live working demo here and the complete project on github here.

Let me know if you need any help with this or if you are stuck somewhere while making it.

Make sure you follow me on twitter and here as well, to get more articles and updates.

Sentry blog image

How I fixed 20 seconds of lag for every user in just 20 minutes.

Our AI agent was running 10-20 seconds slower than it should, impacting both our own developers and our early adopters. See how I used Sentry Profiling to fix it in record time.

Read more

Top comments (0)

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay