Calendar
In this article we will create a calendar using vue.js.
Setup
First create a vue project using vite. After creating the project delete all unnecessary code and files created by vite.
npm init vite <project-name> // creates vue project using vite
cd <project-name> // move in to the project created by vite
npm install // installs all necessary dependecies for our project
npm run dev // runs development server at http://localhost:5173/
Creating calendar
Our calendar will be divided into two main components Header
and calendarGrid
. Header
component will contain name of month, year and chevrons/arrows for changing date.calendarGrid
component will contain days and their corresponding dates. And both of these components will live inside one calendar
component. So, lets first create calendar.vue
in src/components
folder and import Header
and calendarGrid
components into it.
calendar.vue
👇🏻
<script setup>
import Header from './Header.vue'
import calendarGrid from './calendarGrid.vue'
</script>
<template>
<main>
<!-- Using Header and calendarGrid Component -->
<Header/>
<calendarGrid/>
</main>
</template>
<style scoped lang="less">
main{
width: 500px;
padding-bottom: 30px;
border-radius: 5px;
background-color: azure;
box-shadow: 5px 5px 20px 0 rgba(0, 0, 0, 0.5);
@media(max-width: 500px){
width: 90vw;
}
}
</style>
For vue to compile our calendar.vue
we need to import it into App.vue
component because vue passes App.vue
to createApp
function in main.js
for compiling. So, lets do that:
App.vue
👇🏻
<script setup>
import calendar from './components/calendar.vue'
</script>
<template>
<main>
<!-- Using calendar Component -->
<calendar/>
</main>
</template>
Now, lets create Header.vue
component in src/components
folder. In Header.vue
we will create a div
with class header
inside of which we will create further two div
elements with classes info
and wrapper
. .info
element will contain year and .wrapper
element will contain chevrons and name of month. And at last we will add some styles to it.
Header.vue
👇🏻
<script setup>
import chev from '../assets/chev.svg' // importing chevron image
</script>
<template>
<div class="header">
<div class="info">
<div class="year">
<p>2023</p>
</div>
</div>
<div class="wrapper">
<div class="left-chev chev">
<img :src="chev" alt="left-chevron">
</div>
<div class="month">
<h2>February</h2>
</div>
<div class="right-chev chev">
<img :src="chev" alt="right-chevron">
</div>
</div>
</div>
</template>
<style scoped lang="less">
.header{
display: flex;
flex-direction: column;
padding: 10px;
background-color: antiquewhite;
border-radius: 5px 5px 0 0;
.wrapper{
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px 0;
.month{
h2{
font-size: 32px;
font-weight: bolder;
letter-spacing: 5.5px;
}
}
.chev{
user-select: none;
width: 40px;
height: 40px;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
transition: background-color .5s;
&:hover{
background-color: rgb(243, 206, 158);
}
img{
width: 100%;
height: auto;
transform: translateX(2px);
}
}
.left-chev{
transform: rotate(180deg);
}
}
}
</style>
With that we have created Header.vue
. Now, we will create calendarGrid.vue
component.
In calendarGrid
component we will create a div
with calendar-grid
class which will contains two div
elements with classes days-grid
and date-grid
. .days-grid
will contain 7 div
elements with class day
which will contain days of week sunday to saturday. But instead of creating indivisual div
elements we will create an array of week days and we will loop over that in our template
using v-for
directive. .date-grid
element will contain 31, 30, 28 or 29(depending on the month) div
elements with class date
which will contain dates in .val
element. But for now we will render 31 days using range
function and v-for
directive for the sake of layout. We will change it when we will implement functionality for our calendar.
calendarGrid.vue
👇🏻
<script setup>
const DAYS = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
</script>
<template>
<div class="calendar-grid">
<div class="days-grid">
<div
v-for="day, index in DAYS"
:key="index"
class="day"
:title="day"
>{{ day[0] }}</div>
</div>
<div class="date-grid">
<div class="date"
v-for="date in range(31)"
>
<div
class="val">
{{ date }}
</div>
</div>
</div>
</div>
</template>
<style scoped lang="less">
.calendar-grid{
margin-top: 15px;
display: grid;
gap: 20px;
user-select: none;
.days-grid{
display: grid;
grid-template-columns: repeat(7, 1fr);
justify-content: space-between;
align-items: center;
.day{
text-align: center;
font-weight: bold;
&.today{
color: blueviolet;
}
}
}
.date-grid{
display: grid;
grid-template-columns: repeat(7, 1fr);
justify-content: space-between;
align-items: center;
row-gap: 20px;
.date{
display: flex;
justify-content: center;
cursor: pointer;
.val{
width: 20px;
height: 20px;
text-align: center;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
padding: 15px;
position: relative;
background-color: antiquewhite;
}
}
}
}
</style>
And with that layout of our calendar is completed.
Adding Functionality
Basic functionality of our app includes changing dates and to highlight current day and date and also to arrange dates in correct order with respect to their corresponding days. For that we have to create quite a few reactive states and then we have to pass those states from one component to another and evetually we will extend the functionality of our app for adding events for which we have to pass state between sibling components which makes state management bit difficult. So, to manage state easily we will manage states at global level so that any component can access that state. To manage state at global level we can use state managers like vuex
or pinia
but this app not that big and complex so we will use vue's built in provide/inject functions.
Provide function
Vue's provide function allows us to share state/data from higher component to the lower components. E.g: If we have five components A, B C, D and E
where B and C
are children of A
, D
is child of C
and E
is child of D
. Component A
contains a state x
which we want to use in component E
which we can do by passing state x
from A
to C
, C
to D
, D
to E
. In this example, for using state x
in component E
we had to pass it to components C
and D
which do not need this state.This process of passing data/state from parent component to child component (where child component can be at any depth in component tree) as props is known prop drilling.
Component tree with prop drilling
A ----> contains state 'x'
|x <---- passing 'x' from A -> C
|x x x
------------x
| |x
| |x
B C <--- Doest not use 'x'
\x <---- passing 'x' from C -> D
\x
D <--- Doest not use 'x'
\x<---- passing 'x' from D -> E
\x
E ----> uses state 'x'
provide function helps us to avoid prop drilling meaning we can pass state x
directly from A
to E
without prop drilling. Lets see how to do it.
Provide function takes two arguments first is key and second is the state that we want to share. Key is used to access the shared state in child component.
In component A
import {ref, provide} from 'vue'
const x = ref(0)
provide('store', x) // sharing state x and setting 'store' as key
Note: value in provide function can be of any type like string, number, array, object e.t.c
Inject Function
Vue's inject functions allows us to use state shared by parent component in child component. Inject function takes one argument which is the key that we passed to provide function in parent component.
In component E
import {inject} from 'vue'
const x = inject('store') // accessing state 'x'
console.log(x.value) // prints 0
Component tree without prop drilling
A ----> contains state 'x' and share it using provide
|
|
------------
| |
| |
B C
\
\
D
\
\
E ----> uses state 'x' using inject
Lets see how to use provide/inject in our calendar app. Our app have a state called date
which is used in almost every component so, lets make it a global state or app level state. To create app level state we will use provide function inside main.js
.
main.js 👇🏻
import { createApp, ref } from 'vue'
import './less/global.less'
import App from './App.vue'
const date = ref({date: {
str: new Date(),
dateString: new Date().toDateString()
}})
createApp(App)
.provide('store', date) // providing date state to every component in our app.
.mount('#app')
date
is simply an object which contains two properties str
and dateString
. str
store a date object which gives us current date and dateString
gives us date in more simpler format like this 'Sat Feb 27 2023'. But other than date
we have more states in our app that we want to make global. For that we will create a single object which will contain all of our global states. And to keep our main.js
clean and clutter free we will create our global state in different file and then we will import that file into main.js
.
Create store.js
in src/store
folder. store.js
will contain global state.
store.js👇🏻
import { ref } from 'vue'
export const state = ref({
date: {
str: new Date(),
dateString: new Date().toDateString()
}
})
state
in store.js
is a single global reactive state that contains all states for our app. Now, lets import state
in main.js
.
import { createApp } from 'vue'
import './less/global.less'
import App from './App.vue'
import { state } from './store/store.js'
createApp(App)
.provide('store', state) // providing single global state object to every component in our app.
.mount('#app')
Now, we can use state object in any component of our app. Lets use state
in Header.vue
. In Header.vue
we need month
and year
which we can extract from dateString
property of date
object inside state
. We will also create a component level state current
which will contain month
and year
that we will extract from dateString
. One thing to note that dateString
gives only first 3 intials of month name meaning for January
it will give Jan
and so on. But we want full month names so, we will create a MONTHS
object with months first 3 initials as key and months fullname as value that way we can get fullname of months from first 3 initials.
Header.vue👇🏻
<script setup>
import { ref, inject } from 'vue'
import chev from '../assets/chev.svg'
const MONTHS = {
Jan: "January",
Feb: "February",
Mar: "March",
Apr: "April",
May: "May",
Jun: "June",
Jul: "July",
Aug: "August",
Sep: "September",
Oct: "October",
Nov: "November",
Dec: "December"
}
const state = inject('store') // accessing state using inject function
const [ _, month, __, year ] = state.value.date.dateString.split(' ') // extracting month & year from dateString
const current = ref({
month: MONTHS[month], // getting month fullname from MONTHS object using months first 3 initials
year
})
</script>
<template>
<div class="header">
<div class="info">
<div class="year">
<p>{{ current.year }}</p> <!-- Using year from current state above -->
</div>
</div>
<div class="wrapper">
<div class="left-chev chev">
<img :src="chev" alt="left-chevron">
</div>
<div class="month">
<h2>{{ current.month }}</h2> <!-- Using month from current state above -->
</div>
<div class="right-chev chev">
<img :src="chev" alt="right-chevron">
</div>
</div>
</div>
</template>
<style scoped lang="less">
/* styling goes here */
</style>
Until now our calendar is only showing current month and year but we want it to change month and year depending on the chevron we click E.g: if we click on right chevron and current month is february calendar should change it to march and if month is december and year is 2023 then calendar should change month to january and year to 2024. And reverse should happen if we click on left chevron. Now all of this to happen we have to update date
object inside global reactive state
. We can update date
in Header.vue
component but vue documentation recommends not to change global reactive state in child components rather create a function at global level which will be responsible for updaing/changing global state. We will create and export this function from store.js
file and we will name it dispatch
and after that we will import it in main.js
and then instead of passing state
as value to provide
function we will pass an object which contains state
and dispatch
function as properties to provide
function because of which we can access dispatch
in components for updating/changing global state.
store.js👇🏻
import { ref } from 'vue'
export const state = ref({
date: {
str: new Date(),
dateString: new Date().toDateString()
}
})
export const dispatch = () => {} // we will implement it in a bit
main.js👇🏻
import { createApp } from 'vue'
import './less/global.less'
import App from './App.vue'
import { state, dispatch } from './store/store.js' // importing dispatch
createApp(App)
.provide('store', { state, dispatch }) // providing an object containing global state and function to update/change global state to every component in our app.
.mount('#app')
Now, lets understand how dispatch
function will work. In our app we will change date in different ways E.g; clicking on right chevron will change date in inreasing order and clicking on left chevron will change date in decreasing order and we may perform different actions on our app which can change calendar in different ways then how a single function is able to handle every type of change and the answer is that we will create separate functions for each type of action we perform on calendar and dispatch
function will only decide which function to execute. Lets say we have a function increase
which increases the date when we click on right chevron but we cannot invoke increase
function directly because to increase date we have to update date object which is in global state
object and to update global state we have to use dispatch
function so, when user clicks on right chevron we will call dispatch
and then dispatch
will call increase
function and same thing will happen when user clicks left chevron but instead of calling increase
function dispatch
will call decrease
function which decreases the date. So, how does dispatch
knows which function to invoke when user performs any action? When we call dispatch
function we will pass an argument known as action
to it. Based on this argument dispatch
is able to decide which function to call. action
is simply an object with property type
whose value is simply a string which describes what the action is. If we want to increase date by clicking on right chevron for that we will first call dispatch
and pass action
object as an argument like this dispatch({type: 'INCREASE'})
and in dispatch
function we will recieve this action as parameter and inside that function we will check the type
of action
object and if it is INCREASE
it will call increase
function which increases the date. In addition to type
we can also add more properties to action
object if we want to send additional data to dispatch
function.
Lets add click event listeners on right and left chevrons in Header.vue
component. When user clicks on any of the chevron it calls a handleClick
function and we also pass an argument to this function and that argument is 'next-month' or 'previous-month' depending on the chevron user clicked. In handleClick
function we call dispatch
and pass an action
as argument and that argument looks like this
const handleClick = (direction) => dispatch({type: 'updateDate', payload: {direction}})
In above code handleClick
function recieves a parameter direction
which is either 'next-month' or 'previous-month' depending on the chevron user clicked and inside the function we are calling dispatch
with an object as argument and this argument is an action which contains two properties type
and payload
. The value of type
specifies what type of action is being performed meaning in this case user has clicked one of the chevrons so we have to update date either in increasing or decreasing order that is why value of type
is 'updateDate' and payload
is an object which is used to send additional data to our dispatch
function.
Now, inside dispatch
function we have to check the type of the action and based on that we will call an appropriate function for updating date.
store.js👇🏻
import { ref } from 'vue'
export const state = ref({
date: {
str: new Date(),
dateString: new Date().toDateString()
}
})
export const dispatch = (action) => {
// checking the type of action
if(action.type === 'updateDate'){
const newDate = updateDate(state.value.date, action.payload) // calling updateDate function for updating date
state.value = {...state.value, date: newDate} // updating state
}
}
In above code we are checking if action.type
is equal to 'updateDate' and if it is we are calling an updateDate
function which updates date by increasing or decreasing depending on the value of direction
which we passed in payload
object of action
. If value of direction is 'next-month' then updateDate
function will increase date and if value is 'previous-month' then date will be decreased.
Lets implement updateDate
function
const updateDate = (date, payload) => {
const year = date.str.getFullYear()
const month = date.str.getMonth() + 1
let newDate = {}
if(payload.direction === 'next-month') {
newDate = increase(month, year)
}
else newDate = decrease(month, year)
return newDate
}
updateDate
function takes 2 parameters date
and payload
. date
is current date which we will use to get the next date or previous date and payload
is the object which we passed with action to dispatch
and payload
have a direction
property which is used to decide whether to increase the date or decrease. First, we will get current year and month using getFullYear
and getMonth
methods on date.str
and note that getMonth
returns a month number from 0 to 11, 0 -> January & 11 -> December that is why we adding 1 to month. Then we check if direction
equals to 'next-month' and if yes then we increase the date by calling increase
funtion with current month and year as arguments else we decrease date by calling decrease
in same way. And the result object returned by these function is store in newDate variable which is then returned.
As we can see that we are using increase
and decrease
functions inside updateDate
which are responsible for increasing and decreasing date respectively. So, lets implement these functions.
const increase = (month, year) => {
const obj = {}
// checking month is not December👇🏻
if(month !== 11){
obj.str = new Date(`${year}-${month+1}`)
obj.dateString = new Date(`${year}-${month+1}`).toDateString()
}else{
obj.str = new Date(`${year+1}-01`)
obj.dateString = new Date(`${year+1}-01`).toDateString()
}
return obj
}
const decrease = (month, year) => {
const obj = {}
// checking month is not January👇🏻
if(month !== 0){
obj.str = new Date(`${year}-${month-1}`)
obj.dateString = new Date(`${year}-${month-1}`).toDateString()
}else{
obj.str = new Date(`${year-1}-12`)
obj.dateString = new Date(`${year-1}-12`).toDateString()
}
return obj
}
In increase
function we are checking if month
is not December and if it is not then we just have to increase month and year will be same but month
is December then we have to increase year and change month to '01' and the result is stored in obj
object which is then returned. And reverse happens in decrease
function.
Now, when user clicks on any of the chevron it will update date correctly but we will not be able to see those changes on our screen because in Header.vue
we are storing date in component level state known as current
and this state do not know that date has updated so, whenever date updates we also have to update current
for that we have to watch date object in global state so, lets do that using watch
function.
<script setup>
import { ref, inject, watch } from 'vue'
import chev from '../assets/chev.svg'
const MONTHS = {
Jan: "January",
Feb: "February",
Mar: "March",
Apr: "April",
May: "May",
Jun: "June",
Jul: "July",
Aug: "August",
Sep: "September",
Oct: "October",
Nov: "November",
Dec: "December"
}
const state = inject('store') // accessing state using inject function
const [ _, month, __, year ] = state.value.date.dateString.split(' ') // extracting month & year from dateString
const current = ref({
month: MONTHS[month], // getting month fullname from MONTHS object using monts first 3 initials
year
})
// watching date so that we can update current 👇🏻
watch(()=>state.value.date, (updatedDate) => {
const [ _, month, __, year ] = updatedDate.dateString.split(' ') // extracting updated month and year
// updating current👇🏻
current.value = {
month: MONTHS[month],
year
}
})
</script>
<template>
<div class="header">
<div class="info">
<div class="year">
<p>{{ current.year }}</p> <!-- Using year from current state above -->
</div>
</div>
<div class="wrapper">
<div class="left-chev chev">
<img :src="chev" alt="left-chevron">
</div>
<div class="month">
<h2>{{ current.month }}</h2> <!-- Using month from current state above -->
</div>
<div class="right-chev chev">
<img :src="chev" alt="right-chevron">
</div>
</div>
</div>
</template>
<style scoped lang="less">
/* styling goes here */
</style>
watch
function takes 2 arguments first argument is dependency state meaning what state we want to watch and second argument is a function which gets executed when dependency state changes this function takes a parameter which is updated version of dependency state.
Now, we can see the changes on screen
Now, lets add functionality to CalenderGrid.vue
component. In this component we first have to render correct number of days for each month and then we have to make sure that each date falls under correct day in every month. E.g; In 01-february-2023 was 'wednesday' so, in calendar '1' should be under day 'wednesday'. If we dont arrange dates in each month in correct order then in every month '1' will fall under day 'sunday'.
To get the correct number of dates in each month we will create a state called dateGrid
with initial value of empty array inside global reactive state in store.js
. dateGrid
array will be updated with current number of dates every time date changes by clicking on any chevrons in header. To update dateGrid
we will create a function updateDateGrid
which will update dateGrid
whenever date changes.
store.js👇🏻
import { ref } from 'vue'
export const state = ref({
date: {
str: new Date(),
dateString: new Date().toDateString()
},
dateGrid: []
})
export const dispatch = (action) => {
// checking the type of action
if(action.type === 'updateDate'){
const newDate = updateDate(state.value.date, action.payload) // calling updateDate function for updating date
state.value = {...state.value, date: newDate} // updating state
}
}
Now, lets implement updateDateGrid
function. This function recieves an argument which is current date object then we are extracting month name and year from dateString
property of date object. After that we need to know number of days in current month for that we are using getNumberOfDays
function and we are passing month name as an argument to this function. After recieving number of days in numberOfDays
variable from getNumberOfDays
function and then we are creating currentMonthDates
array which will contain dates of current month. Each element in currentMonthDates
array will be an object with three properties id
, dt
, day
. dt
is date from 1 to numberOfDays
, day
is simply day on particular date dt
.
const DAYS = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
const updateDateGrid = date => {
const [ _, monthName, __, year ] = date.dateString.split(' ') // extracting month name and year
const numberOfDays = getNumberOfDays(monthName, year) // recieving number of days
const currentMonthDates = [] // will contain dates of current month
const monthNumber = date.str.getMonth() + 1 // current month number 1 - 12
for(let i = 0; i < numberOfDays; i++){
const item = {} // creating an object that will contain day & date
const day = new Date(`${year}-${monthNumber}-${i+1}`).getDay() // getting day number 0 - 6
item.id = i + 1
item.dt = i + 1
item.day = DAYS[day] // getting day from DAYS array
currentMonthDates.push(item) // adding item object containing day and date to currentMonthDates array
}
return currentMonthDates
}
In above code you can see that we are using getNumberOfDays
function which returns number of days in given month lets implement this function.
const getNumberOfDays = (month, year) => {
const thirtyOneDays = ['Jan', 'Mar', 'May', 'Jul', 'Aug', 'Oct', 'Dec'] // months having 31 days
if(thirtyOneDays.includes(month)) return 31
else if(month === 'Feb') return isLeapYear(year) ? 29 : 28 // checking if year is a leap year
return 30
}
First we are creating an array of months thirtyOneDays
that have 31 days then we are checking that if the given month is in this array and if it is then we are returning 31
but if it is not in this array then we are checking that if month is February and if it is then we have to check that if the year is leap year or not if it is leap year then we are returning 29
else we are returning 28
but if month is not February then we return 30
.
In above code we have used isLeapYear
function which returns true
if the year is a leap year else it returns false
. Now, lets implement it.
const isLeapYear = year => {
const firstCondition = year % 4 === 0 && year % 100 !== 0 // If a year is divisible by 4 and is not divisible by 100
const secondCondition = year % 4 === 0 && year % 100 === 0 && year % 400 === 0 // If a year is divisible by 4 and 100 and 400
if(firstCondition || secondCondition) return true
return false
}
A year is leap if it specifies two condition:
- If a year is divisible by 4 and is not divisible by 100.
- If a year is divisible by 4 and 100 and 400.
Now, our calendar renders correct number of days for each month but there is still a problem which is that these days are not in correct order meaning the first date of every month comes under sunday as shown in picture above.
Now, lets arrange these dates in correct order and for that we will create a function which takes currentMonthDates
as a parameter then it first checks the day on first date which it gets from first element of array. Then we will get index of that day from DAYS
array which will let us know at which position we have to render the first date in our calendar. E.g: day on first date of February is Wednesday
so the date 1
should be under W
in calendar for that we will first get the index of Wed
from DAYS
array and the index is 3
. So, we will prepend 3 empty objects in currentMonthDates
array so that when we loop over currentMonthDates
array it will render 3 empty objects which will leave first 3 blocks in calendar empty and the date 1
will be rendered on 4th block which is under W
and all other dates automatically falls under correct day. And same process will be repeated for each month.
Lets implement this function
const DAYS = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
const arrangeGridAccordingToDays = currentMonthDates => {
let newDateGrid = []
const firstDay = DAYS.indexOf(currentMonthDates[0].day) // getting index of first day
for(let i = 0; i <= firstDay; i++){
if(i < firstDay){
newDateGrid = [...newDateGrid, {
id: Math.random(),
dt: '', // adding empty date value
day: DAYS[i]
}]
}else{
newDateGrid = [...newDateGrid, ...currentMonthDates]
}
}
return newDateGrid
}
Now, use above function in updateDateGrid
function.
const DAYS = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
const updateDateGrid = date => {
const [ _, monthName, __, year ] = date.dateString.split(' ') // extracting month name and year
const numberOfDays = getNumberOfDays(monthName, year) // recieving number of days
const currentMonthDates = [] // will contain dates of current month
const monthNumber = date.str.getMonth() + 1 // current month number 1 - 12
for(let i = 0; i < numberOfDays; i++){
const item = {} // creating an object that will contain day & date
const day = new Date(`${year}-${monthNumber}-${i+1}`).getDay() // getting day number 0 - 6
item.id = i + 1
item.dt = i + 1
item.day = DAYS[day] // getting day from DAYS array
currentMonthDates.push(item) // adding item object containing day and date to currentMonthDates array
}
return arrangeGridAccordingToDays(currentMonthDates) // arranging dates in correct order
}
CalenderGrid.vue 👇🏻
<script setup>
import { ref, computed, inject } from 'vue'
const DAYS = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
const {state, dispatch} = inject('store')
const current = ref({
currentDay: DAYS[new Date().getDay()],
currentDate: new Date().getDate(),
currentMonth: new Date().getMonth(),
currentYear: new Date().getFullYear()
})
// Checking current month and year 👇🏻
const isCurrentMonthAndYear = computed(() => (
state.value.date.str.getMonth() === current.value.currentMonth &&
state.value.date.str.getFullYear() === current.value.currentYear
))
</script>
<template>
<div class="calender-grid">
<div class="days-grid">
<div
v-for="day, index in DAYS"
:key="index"
:class="['day', {today: day === current.currentDay && isCurrentMonthAndYear}]"
:title="day"
>{{ day[0] }}</div>
</div>
<div class="date-grid">
<div class="date"
v-for="date in state.dateGrid" // Looping over dateGrid
:key="date.id"
>
<div
:class="['val', {today: date.dt === current.currentDate && isCurrentMonthAndYea}]"
v-if="date.dt">
{{ date.dt }}
</div>
<div v-else>❌</div>
</div>
</div>
</div>
</template>
<style scoped lang="less">
.calender-grid{
margin-top: 15px;
display: grid;
gap: 20px;
user-select: none;
.days-grid{
display: grid;
grid-template-columns: repeat(7, 1fr);
justify-content: space-between;
align-items: center;
.day{
text-align: center;
font-weight: bold;
&.today{
color: blueviolet;
}
}
}
.date-grid{
display: grid;
grid-template-columns: repeat(7, 1fr);
justify-content: space-between;
align-items: center;
row-gap: 20px;
.date{
display: flex;
justify-content: center;
cursor: pointer;
.val{
width: 20px;
height: 20px;
text-align: center;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
padding: 15px;
position: relative;
background-color: antiquewhite;
&.today{
color: blueviolet;
font-weight: bold;
}
&.event{
// border: 1px solid salmon;
background-color: antiquewhite;
font-weight: bold;
&::before{
content: '';
width: 5px;
height: 5px;
border-radius: 50%;
background-color: salmon;
position: absolute;
top: 2px;
right: 2px;
}
}
}
}
}
}
</style>
And with that our basic calendar is ready.
Top comments (2)
Thank you for sharing your experience with this task. It seems like not a trivial task, indeed. I was wondering have you considered using some ready component instead of building it from scratch?
I am glad that you enjoyed it.... I built it from scratch only with vue.js