Appearance
Appointments Widget
Appointments API
Importing the Appointment Widget
The appointment widget uses Vue 2 and requires a few scripts in order to run properly.
Place the following scripts in the <head>
of your project:
html
<head>
<!-- -->
<script>
const vueApp = {}
</script>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
</head>
For the calendar.js script, create a copy instead of directly importing it so that you can make modifications to the script based on your specific needs. Make sure to adjust the paths so that it loads from your servers in case you have copied it.
Place the calendar.js script in the <body>
of your project:
html
<body>
<!-- -->
<script src="https://res.bildhive.com/scripts/calendar.js"></script>
</body>
(Optional) The sample code makes use of the Tailwind CSS framework. Feel free to also import this into your project should you want to use it as well.
html
<head>
<!-- -->
<link rel="stylesheet" href="https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css">
<!-- -->
</head>
Sample Appointment Widget
Place the following <style>
in the <body>
of your project if you plan to use this sample appointment widget instead of creating your own custom one.
html
<body>
<style>
.custom-scrollbar::-webkit-scrollbar {
width: 5px;
height: 5px;
}
.custom-scrollbar::-webkit-scrollbar-track {
background: #F2F2F2;
}
.custom-scrollbar::-webkit-scrollbar-thumb {
background: #D0C9D6;
border-radius: 10px;
}
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
background: #ffa500;
}
.wrapper {
margin: 15px 0;
max-width: 1100px;
}
.container-calendar {
background: #ffffff;
padding: 15px;
max-width: 475px;
margin: 0 auto;
overflow: auto;
}
.table-calendar {
border-collapse: collapse;
width: 100%;
}
.table-calendar th {
font-weight: normal;
padding: 0.5em;
border-bottom: 4px solid #e2e2e2;
text-align: center;
vertical-align: top;
}
.table-calendar td {
cursor: pointer;
padding: 5px;
border: none;
text-align: center;
position: relative;
vertical-align: top;
height: 60px;
width: 60px;
}
.date-picker {
position: relative;
}
.date-picker div {
height: 100%;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
}
.date-picker.disabled {
user-select: none;
cursor: default;
opacity: 0.3;
}
.date-picker.selected div {
font-weight: bold;
border-radius: 50%;
background-color: #9693e7;
display: flex;
justify-content: center;
align-items: center;
color: white;
width: 100%;
height: 100%;
}
.date-picker:nth-child(1) {
color: #ffa500;
}
.date-picker:nth-child(7) {
color: #ffa500;
}
#monthAndYear {
text-align: center;
margin-top: 0;
}
#previous {
float: left;
}
#next {
float: right;
}
.month-arrows {
color: #444c63;
transition: color 0.2s ease-in-out;
}
.month-arrows:hover {
color: #ffa500;
transition: color 0.2s ease-in-out;
}
.calendar-select-button {
border-radius: 10px;
border: 1.5px solid #D0C9D6;
background-color: transparent;
color: #444C63;
transition: background-color 0.2s ease-in-out, color 0.2s ease-in-out;
}
.calendar-select-button.active {
background-color: #ffa500;
color: white;
transition: background-color 0.2s ease-in-out, color 0.2s ease-in-out;
}
.calendar-button {
border-radius: 10px;
border: 1.5px solid #ffa500;
color: #ffa500;
background-color: transparent;
transition: background-color 0.2s ease-in-out, color 0.2s ease-in-out;
}
.calendar-button:hover {
background-color: #ffa500;
color: white !important;
transition: background-color 0.2s ease-in-out, color 0.2s ease-in-out;
}
.time-buttons:not(:first-child) {
margin-top: 0.75em;
}
.appointment-list:not(:first-child) {
margin-top: 1em;
}
.appointment-form-container {
color: #444c63;
}
.appointment-form-container:not(:first-child) {
margin-top: 1em;
}
.guest-options:hover {
color: #ffa500;
}
.appointment-form-input {
height: 45px;
width: 100%;
background-color: #F6F9FD;
color: #444c63;
border-radius: 10px;
border: 1px solid #D0C9D6;
}
/* Loading Classes */
/* Absolute Center Spinner */
.loading {
position: absolute;
z-index: 999;
height: 2em;
width: 2em;
overflow: visible;
margin: auto;
top: 0;
left: 0;
bottom: 0;
right: 0;
}
/* :not(:required) hides these rules from IE9 and below */
.loading:not(:required) {
/* hide "loading..." text */
font: 0/0 a;
color: transparent;
text-shadow: none;
background-color: transparent;
border: 0;
}
.loading:not(:required):after {
content: '';
display: block;
font-size: 10px;
width: 1em;
height: 1em;
margin-top: -0.5em;
-webkit-animation: spinner 1500ms infinite linear;
-moz-animation: spinner 1500ms infinite linear;
-ms-animation: spinner 1500ms infinite linear;
-o-animation: spinner 1500ms infinite linear;
animation: spinner 1500ms infinite linear;
border-radius: 0.5em;
-webkit-box-shadow: rgba(0, 0, 0, 0.75) 1.5em 0 0 0, rgba(0, 0, 0, 0.75) 1.1em 1.1em 0 0, rgba(0, 0, 0, 0.75) 0 1.5em 0 0, rgba(0, 0, 0, 0.75) -1.1em 1.1em 0 0, rgba(0, 0, 0, 0.5) -1.5em 0 0 0, rgba(0, 0, 0, 0.5) -1.1em -1.1em 0 0, rgba(0, 0, 0, 0.75) 0 -1.5em 0 0, rgba(0, 0, 0, 0.75) 1.1em -1.1em 0 0;
box-shadow: rgba(0, 0, 0, 0.75) 1.5em 0 0 0, rgba(0, 0, 0, 0.75) 1.1em 1.1em 0 0, rgba(0, 0, 0, 0.75) 0 1.5em 0 0, rgba(0, 0, 0, 0.75) -1.1em 1.1em 0 0, rgba(0, 0, 0, 0.75) -1.5em 0 0 0, rgba(0, 0, 0, 0.75) -1.1em -1.1em 0 0, rgba(0, 0, 0, 0.75) 0 -1.5em 0 0, rgba(0, 0, 0, 0.75) 1.1em -1.1em 0 0;
}
/* Animation */
@-webkit-keyframes spinner {
0% {
-webkit-transform: rotate(0deg);
-moz-transform: rotate(0deg);
-ms-transform: rotate(0deg);
-o-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
-moz-transform: rotate(360deg);
-ms-transform: rotate(360deg);
-o-transform: rotate(360deg);
transform: rotate(360deg);
}
}
@-moz-keyframes spinner {
0% {
-webkit-transform: rotate(0deg);
-moz-transform: rotate(0deg);
-ms-transform: rotate(0deg);
-o-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
-moz-transform: rotate(360deg);
-ms-transform: rotate(360deg);
-o-transform: rotate(360deg);
transform: rotate(360deg);
}
}
@-o-keyframes spinner {
0% {
-webkit-transform: rotate(0deg);
-moz-transform: rotate(0deg);
-ms-transform: rotate(0deg);
-o-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
-moz-transform: rotate(360deg);
-ms-transform: rotate(360deg);
-o-transform: rotate(360deg);
transform: rotate(360deg);
}
}
@keyframes spinner {
0% {
-webkit-transform: rotate(0deg);
-moz-transform: rotate(0deg);
-ms-transform: rotate(0deg);
-o-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
-moz-transform: rotate(360deg);
-ms-transform: rotate(360deg);
-o-transform: rotate(360deg);
transform: rotate(360deg);
}
}
</style>
<!-- -->
</body>
Sample HTML
Replace {{appointmentInstance}}
with the instance ID that you would like to pull appointment settings from.
html
<div data-section-type="calendar" data-instance="{{appointmentInstance}}" class="w-full">
<div id="app-{{id}}" v-cloak class="relative container mx-auto">
<div v-if="loading" class="loading-overlay-drawer" :class="loading? 'show' : ''"
style="position:absolute; top:0; left:0; width:100%; height:100%; z-index:100000;display:flex;place-items:center;place-content:center;background:rgba(255,255,255,0.8)">
<div class="loading"></div>
</div>
<div class="w-full relative mx-auto" style="padding: 0 5%; max-width: 1200px;">
<div v-show="step === 0" class="grid grid-cols-1 items-center gap-5"
:class="[selectedDate && selectedDate.date ? 'lg:grid-cols-2' : '']">
<div class="wrapper">
<div class="container-calendar">
<div class="mb-8 md-text-size text-center" style="color: #444c63;">{{headerText}}</div>
<div class="mb-8 flex justify-center">
<div class="inline-flex items-center">
<button @click="meetingTypeSelect(type)" v-for="(type,typeI) in meetingTypes"
class="px-5 py-2 calendar-select-button"
:class="selectedType.id === type.id ? 'active' : ''"
:key="type+typeI">%{type.name}</button>
</div>
</div>
<div class="flex items-center justify-center mb-3">
<button class="mr-3 month-arrows" id="previous" @click="previous"><svg
xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
stroke-linejoin="round" class="feather feather-chevron-left">
<polyline points="15 18 9 12 15 6"></polyline>
</svg></button>
<div id="monthAndYear" style="font-size: 20px;"></div>
<button class="ml-3 month-arrows" id="next" @click="next"><svg
xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
stroke-linejoin="round" class="feather feather-chevron-right">
<polyline points="9 18 15 12 9 6"></polyline>
</svg></button>
</div>
<table class="table-calendar" id="calendar">
<thead id="thead-month"></thead>
<tbody id="calendar-body"></tbody>
</table>
</div>
</div>
<div v-show="selectedDate && selectedDate.date">
<div class="font-bold mb-3" style="color: #444c63;">How long do you need?</div>
<div class="inline-flex items-center">
<button @click="durationSelect(time)" v-for="(time,timeI) in durationSlots"
class="px-5 py-2 calendar-select-button"
:class="selectedDuration.value === time.value ? 'active' : ''"
:key="time+timeI">%{time.text}</button>
</div>
<div v-show="Object.entries(selectedDuration).length != 0">
<div class="font-bold mb-3 mt-8" style="color: #444c63;">What time works best?</div>
<select v-model="selectedTimezone" class="mb-5" @change="updateAvailability()">
<option v-for="(time,timeI) in timeZones" :key="time+timeI" :value="time.value">
%{time.label}</option>
</select>
<div class="flex-1 flex flex-col custom-scrollbar pr-3"
style="overflow-y: scroll; height: 275px;">
<button @click="timeSelect(time)" class="time-buttons calendar-select-button px-5 py-2"
v-for="(time,timeI) in availableTimeSlots" :class="selectedTime == time ? 'active' : ''"
:key="time+timeI">%{convertStringTime(time.text)}</button>
</div>
</div>
</div>
</div>
<div v-show="step === 1">
<div class="inline-block">
<button @click="step = 0" class="flex-1 px-5 py-2 calendar-button"
style="border-radius: 10px; border: 1.5px solid #ffa500; color: #ffa500;">
<div class="flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
stroke-linejoin="round" class="feather feather-chevron-left">
<polyline points="15 18 9 12 15 6"></polyline>
</svg>
BACK
</div>
</button>
</div>
<div class="grid grid-cols-1 lg:grid-cols-2 items-center gap-5">
<div class="wrapper">
<div class="mb-8 md-text-size text-center" style="color: #ffa500;">{{headerText}}</div>
<div class="appointment-list flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"
fill="none" stroke="#ffa500" stroke-width="2" stroke-linecap="round"
stroke-linejoin="round" class="feather feather-clock">
<circle cx="12" cy="12" r="10"></circle>
<polyline points="12 6 12 12 16 14"></polyline>
</svg>
<div class="ml-2" style="color: #444c63;">%{selectedDuration.text}</div>
</div>
<div class="appointment-list flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"
fill="none" stroke="#ffa500" stroke-width="2" stroke-linecap="round"
stroke-linejoin="round" class="feather feather-map-pin">
<path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"></path>
<circle cx="12" cy="10" r="3"></circle>
</svg>
<div class="ml-2" style="color: #444c63;">Meeting link to be sent later</div>
</div>
<div class="appointment-list flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"
fill="none" stroke="#ffa500" stroke-width="2" stroke-linecap="round"
stroke-linejoin="round" class="feather feather-calendar">
<rect x="3" y="4" width="18" height="18" rx="2" ry="2"></rect>
<line x1="16" y1="2" x2="16" y2="6"></line>
<line x1="8" y1="2" x2="8" y2="6"></line>
<line x1="3" y1="10" x2="21" y2="10"></line>
</svg>
<div class="ml-2" style="color: #444c63;">%{convertTime(selectedTime.start)} -
%{convertTime(selectedTime.end)}, %{strDate()}</div>
</div>
<div class="appointment-list flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"
fill="none" stroke="#ffa500" stroke-width="2" stroke-linecap="round"
stroke-linejoin="round" class="feather feather-globe">
<circle cx="12" cy="12" r="10"></circle>
<line x1="2" y1="12" x2="22" y2="12"></line>
<path
d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z">
</path>
</svg>
<div class="ml-2" style="color: #444c63;">%{findTimezone()}</div>
</div>
</div>
<div>
<div class="mb-8 md-text-size text-left">Enter Details</div>
<div class="appointment-form-container">
<div class="mb-2">First Name<span style="color: red;">*</span></div>
<input :disabled="reschedule" v-model="formObj.customer.firstName" type="text"
name="firstName" id="{{field.id}}" class="appointment-form-input pl-3 pr-3"
:style="[!checkFields.firstName ? {'border-color': 'red'} : {'':''}]"
placeholder="First Name" required />
<div v-if="!checkFields.firstName" class="xs-text-size" style="color: red;">Please enter a
first name</div>
</div>
<div class="appointment-form-container">
<div class="mb-2">Last Name<span style="color: red;">*</span></div>
<input :disabled="reschedule" v-model="formObj.customer.lastName" type="text"
name="lastName" id="{{field.id}}" class="appointment-form-input pl-3 pr-3"
:style="[!checkFields.lastName ? {'border-color': 'red'} : {'':''}]"
placeholder="Last Name" required />
<div v-if="!checkFields.lastName" class="xs-text-size" style="color: red;">Please enter a
last name</div>
</div>
<div class="appointment-form-container">
<div class="mb-2">Email Address<span style="color: red;">*</span></div>
<input :disabled="reschedule" v-model="formObj.customer.email" type="email" name="email"
id="{{field.id}}" class="appointment-form-input pl-3 pr-3"
:style="[!checkFields.email ? {'border-color': 'red'} : {'':''}]"
placeholder="Email Address" required
pattern="[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$" />
<div v-if="!checkFields.email" class="xs-text-size" style="color: red;">Please enter an
email</div>
</div>
<div class="appointment-form-container">
<div class="mb-2">Guest Email(s)</div>
<div @click.away="clearSearch()" @keydown.escape="clearSearch()">
<div class="relative" @keydown.enter.prevent="addEmail(textInput)">
<form autocomplete="off">
<input v-model="textInput" @input="search($event.target.value)"
autocomplete="off" name="guests" id="{{field.id}}"
class="appointment-form-input pl-3 pr-3" placeholder="Guest Email(s)"
pattern="[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$" />
</form>
<div :class="[open ? 'block' : 'hidden']">
<div class="absolute z-40 left-0 mt-2 w-full">
<div class="py-1 text-sm bg-white rounded shadow-lg border border-gray-300">
<a @click.prevent="addEmail(textInput)"
class="block py-1 px-5 cursor-pointer hover:bg-indigo-600 hover:text-white">Add
email "<span class="font-semibold">%{textInput}</span>"</a>
</div>
</div>
</div>
<!-- selections -->
<template v-for="(email, emailI) in formObj.guests">
<div class="bg-indigo-100 inline-flex items-center text-sm rounded mt-2 mr-1"
:key="email+emailI">
<span class="ml-2 mr-1 leading-relaxed truncate max-w-xs">%{email}</span>
<button @click.prevent="removeEmail(emailI)"
class="w-6 h-8 inline-block align-middle text-gray-500 guest-options focus:outline-none">
<svg class="w-6 h-6 fill-current mx-auto"
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path fill-rule="evenodd"
d="M15.78 14.36a1 1 0 0 1-1.42 1.42l-2.82-2.83-2.83 2.83a1 1 0 1 1-1.42-1.42l2.83-2.82L7.3 8.7a1 1 0 0 1 1.42-1.42l2.83 2.83 2.82-2.83a1 1 0 0 1 1.42 1.42l-2.83 2.83 2.83 2.82z" />
</svg>
</button>
</div>
</template>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="flex justify-end mt-12">
<div v-if="Object.entries(selectedDuration).length != 0 && Object.entries(selectedTime).length != 0 && step == 0"
class="inline-block">
<button @click="step = 1" class="flex-1 px-5 py-2 calendar-button"
style="border-radius: 10px; border: 1.5px solid #ffa500; color: #ffa500;">CONTINUE</button>
</div>
<div v-if="step == 1" class="inline-block">
<button @click="submit" class="flex-1 px-5 py-2 calendar-button"
style="border-radius: 10px; border: 1.5px solid #ffa500; color: #ffa500;">SCHEDULE
MEETING</button>
</div>
</div>
</div>
</div>
</div>
API
If you would like to create your own custom appointment widget, you can review api specific documentation here.