A custom Button component built using Vue and Tailwindcss for an ongoing opensource project.
Designed by @mithicher
A Pen by abhishek sarmah on CodePen.
<div id="app"> | |
<div class="h-2 w-100 bg-blue-500"></div> | |
<div class="max-w-lg mx-auto my-12"> | |
<!-- primary started --> | |
<h2 class="flex text-xl mb-3 text-gray-700"> | |
<span class="text-lg bg-gray-200 text-gray-800 h-6 w-6 rounded-full inline-flex justify-center items-center mt-px mr-3">1</span> | |
Primary Loading Button | |
</h2> | |
<loading-button ref="primaryLoadingButton" @click="click('primaryLoadingButton')">Save Changes</loading-button> | |
<h2 class="flex text-xl mb-3 text-gray-700 mt-6"> | |
<span class="text-lg bg-gray-200 text-gray-800 h-6 w-6 rounded-full inline-flex justify-center items-center mt-px mr-3">2</span> | |
Primary Disabled Button | |
</h2> | |
<loading-button ref="primaryDisabledButton" @click="click" :disabled="true">Save Changes</loading-button> | |
<h2 class="flex text-xl mb-3 text-gray-700 mt-6"> | |
<span class="text-lg bg-gray-200 text-gray-800 h-6 w-6 rounded-full inline-flex justify-center items-center mt-px mr-3">3</span> | |
Primary Outline Button | |
</h2> | |
<loading-button ref="primaryOutlineButton" @click="alert('Editing work :)')" variant-type="outline">Edit</loading-button> | |
<!-- primary ended --> | |
<!-- danger started --> | |
<h2 class="flex text-xl mb-3 text-gray-700 mt-6"> | |
<span class="text-lg bg-gray-200 text-gray-800 h-6 w-6 rounded-full inline-flex justify-center items-center mt-px mr-3">4</span> | |
Danger Loading Button | |
</h2> | |
<loading-button ref="dangerLoadingButton" @click="click('dangerLoadingButton')" variant="danger">Confirm Delete</loading-button> | |
<h2 class="flex text-xl mb-3 text-gray-700 mt-6"> | |
<span class="text-lg bg-gray-200 text-gray-800 h-6 w-6 rounded-full inline-flex justify-center items-center mt-px mr-3">5</span> | |
Danger Disabled Button | |
</h2> | |
<loading-button ref="dangerDisabledButton" @click="click" :disabled="true" variant="danger">Delete Project</loading-button> | |
<h2 class="flex text-xl mb-3 text-gray-700 mt-6"> | |
<span class="text-lg bg-gray-200 text-gray-800 h-6 w-6 rounded-full inline-flex justify-center items-center mt-px mr-3">6</span> | |
Danger Outline Button | |
</h2> | |
<loading-button ref="primaryOutlineButton" @click="alert('Delete work :)')" variant-type="outline" variant="danger">Delete</loading-button> | |
<!-- danger ended --> | |
<!-- success started --> | |
<h2 class="flex text-xl mb-3 text-gray-700 mt-6"> | |
<span class="text-lg bg-gray-200 text-gray-800 h-6 w-6 rounded-full inline-flex justify-center items-center mt-px mr-3">7</span> | |
Success Loading Button | |
</h2> | |
<loading-button ref="successLoadingButton" @click="click('successLoadingButton')" variant="success">Pay Now</loading-button> | |
<h2 class="flex text-xl mb-3 text-gray-700 mt-6"> | |
<span class="text-lg bg-gray-200 text-gray-800 h-6 w-6 rounded-full inline-flex justify-center items-center mt-px mr-3">8</span> | |
Success Disabled Button | |
</h2> | |
<loading-button ref="dangerDisabledButton" @click="click" :disabled="true" variant="success">Processing ...</loading-button> | |
<h2 class="flex text-xl mb-3 text-gray-700 mt-6"> | |
<span class="text-lg bg-gray-200 text-gray-800 h-6 w-6 rounded-full inline-flex justify-center items-center mt-px mr-3">9</span> | |
Success Outline Button | |
</h2> | |
<loading-button ref="primaryOutlineButton" @click="alert('Signing up:)')" variant-type="outline" variant="success">Sign Up</loading-button> | |
<!-- success ended --> | |
<!-- warning started --> | |
<h2 class="flex text-xl mb-3 text-gray-700 mt-6"> | |
<span class="text-lg bg-gray-200 text-gray-800 h-6 w-6 rounded-full inline-flex justify-center items-center mt-px mr-3">10</span> | |
Warning Button | |
</h2> | |
<loading-button ref="warningLoadingButton" @click="click('warningLoadingButton')" variant="warning">Warning !</loading-button> | |
<!-- warning ended --> | |
<!-- secondary started --> | |
<h2 class="flex text-xl mb-3 text-gray-700 mt-6"> | |
<span class="text-lg bg-gray-200 text-gray-800 h-6 w-6 rounded-full inline-flex justify-center items-center mt-px mr-3">11</span> | |
Secondary Button | |
</h2> | |
<loading-button ref="secondaryLoadingButton" @click="click('secondaryLoadingButton')" variant="secondary" variant-type="outline">Cancel</loading-button> | |
<!-- secondary ended --> | |
</div> | |
</div> |
const LoadingButton = { | |
name: "LoadingButton", | |
template: ` | |
<component | |
:is="tag" | |
:type="type" | |
v-on="$listeners" | |
:disabled="disableButton" | |
:class="[btnClass, colorVariants]" | |
:variant="variant" | |
:variant-type="variantType" | |
:size="size" | |
:href="to" | |
> | |
<slot /> | |
</component>`, | |
props: { | |
tag: { | |
type: String, | |
default: "button" | |
}, | |
disabled: { | |
type: Boolean, | |
default: false | |
}, | |
variant: { | |
type: String, | |
default: "primary" | |
}, | |
variantType: { | |
type: String, | |
default: "normal" | |
}, | |
size: { | |
type: String, | |
default: "normal" | |
}, | |
rounded: { | |
type: String, | |
default: "medium" | |
}, | |
type: { | |
type: String, | |
default: "" | |
}, | |
to: { | |
type: String | |
} | |
}, | |
data() { | |
return { | |
loading: false, | |
disableButton: this.disabled | |
}; | |
}, | |
methods: { | |
startLoading() { | |
this.loading = true; | |
this.disableButton = true; | |
}, | |
stopLoading() { | |
this.loading = false; | |
this.disableButton = false; | |
} | |
}, | |
computed: { | |
btnClass() { | |
return { | |
"base-spinner": this.loading == true, | |
"cursor-not-allowed": this.disableButton == true, | |
"base-button inline-flex align-middle align-items-center justify-center font-medium focus:outline-none border-2": true, | |
"rounded-lg": this.rounded === "medium", | |
"rounded-full": this.rounded === "large", | |
"px-6 py-3": this.size == "normal", | |
"px-4 py-2": this.size == "small" | |
}; | |
}, | |
colorVariants() { | |
switch (this.variant) { | |
case "primary": | |
switch (this.variantType) { | |
case "normal": | |
switch (this.disableButton) { | |
case true: | |
return "border-blue-300 bg-blue-300 text-white"; | |
break; | |
default: | |
break; | |
} | |
return "border-blue-600 bg-blue-600 hover:bg-blue-700 hover:border-blue-700 text-white"; | |
break; | |
case "outline": | |
return "border-gray-200 text-blue-500 hover:text-blue-700"; | |
break; | |
default: | |
break; | |
} | |
break; | |
case "danger": | |
switch (this.variantType) { | |
case "normal": | |
switch (this.disableButton) { | |
case true: | |
return "border-red-300 bg-red-300 text-white"; | |
break; | |
default: | |
break; | |
} | |
return "border-red-600 bg-red-600 hover:bg-red-700 hover:border-red-700 text-white"; | |
break; | |
case "outline": | |
return "border-gray-200 text-red-500 hover:text-red-600"; | |
break; | |
default: | |
break; | |
} | |
break; | |
case "warning": | |
switch (this.variantType) { | |
case "normal": | |
return "border-orange-600 bg-orange-600 hover:bg-orange-700 hover:border-orange-700 text-white"; | |
break; | |
default: | |
break; | |
} | |
break; | |
case "success": | |
switch (this.variantType) { | |
case "normal": | |
switch (this.disableButton) { | |
case true: | |
return "border-green-300 bg-green-300 text-white"; | |
break; | |
default: | |
break; | |
} | |
return "border-green-600 bg-green-600 hover:bg-green-700 hover:border-green-700 text-white"; | |
break; | |
case "outline": | |
return "border-2 border-gray-200 text-green-500 hover:text-green-700"; | |
break; | |
default: | |
break; | |
} | |
break; | |
case "white": | |
switch (this.variantType) { | |
case "normal": | |
return "border-white bg-white bg-white text-blue-600 hover:text-blue-800"; | |
break; | |
default: | |
break; | |
} | |
break; | |
case "secondary": | |
switch (this.variantType) { | |
case "outline": | |
return "border-gray-300 text-gray-500 hover:text-blue-500"; | |
break; | |
default: | |
break; | |
} | |
break; | |
default: | |
break; | |
} | |
} | |
} | |
}; | |
new Vue({ | |
el: "#app", | |
components: { | |
LoadingButton | |
}, | |
methods: { | |
click(ref) { | |
this.$refs[`${ref}`].startLoading(); | |
setTimeout(() => this.$refs[`${ref}`].stopLoading(), 3000); | |
} | |
} | |
}); |
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.11/vue.min.js"></script> |
.base-button + .base-button { | |
margin-left: 1em; | |
} | |
@keyframes spinner { | |
to { | |
transform: rotate(360deg); | |
} | |
} | |
.base-spinner { | |
position: relative; | |
overflow: hidden; | |
} | |
.base-spinner:before { | |
content: ""; | |
box-sizing: border-box; | |
position: absolute; | |
background-color: inherit; | |
width: 100%; | |
height: 100%; | |
display: block; | |
z-index: 1; | |
top: 0; | |
left: 0; | |
} | |
.base-spinner:after { | |
content: ""; | |
box-sizing: border-box; | |
position: absolute; | |
top: 50%; | |
left: 50%; | |
width: 20px; | |
height: 20px; | |
margin-top: -10px; | |
margin-left: -10px; | |
border-radius: 50%; | |
border: 2px solid rgba(255, 255, 255, 0.45); | |
border-top-color: inherit; | |
animation: spinner 0.6s linear infinite; | |
z-index: 2; | |
} |
<link href="https://cdnjs.cloudflare.com/ajax/libs/tailwindcss/1.2.0/tailwind.min.css" rel="stylesheet" /> |