DEV Community

Cover image for Building a Custom Calendar with React and Day.js: A Step-by-Step Guide.
Oluwadahunsi A.
Oluwadahunsi A.

Posted on

Building a Custom Calendar with React and Day.js: A Step-by-Step Guide.

Welcome!

This is the first of a three-part series on building a custom calendar with React and Day.js. We are going to start with a simple calendar in this first part and go all the way up to a date range picker in the third part.

When you are done with this, you can check the second part here:

And the third part here:

Here, we will create a simple, yet functional calendar using React and Day.js, then we will add more features and functionalites in the subsequent parts.

By the end of this part, you will have a basic calendar that looks like this:

A simple Calendar

By the end of the third part, we will have a Date Range Picker created from this calendar. Here is a sneak peak:

Custom Date Range Picker

Try to follow along as much as you can. I have also provided the final version for this part at the end of this article. So, let us dive into creating a dynamic calendar application!

Setting up our React project with Vite.

To kickstart our React project using Vite, let's begin by opening our terminal and running the following command:

npm create vite@latest
Enter fullscreen mode Exit fullscreen mode

If you prefer, you can name the project “Calendar”. Otherwise, you are free to choose a name that suits you. Also, make sure to select Typescript while setting up vite for your application.

Project name: Use any name you prefer.

Framework: React.

Language: TypeScript.

Enter fullscreen mode Exit fullscreen mode

Once you are all set, create a Calendar folder within your src directory. Inside the Calendar folder, create two files: Calendar.tsx for building our Calendar component and style.css for our styling needs. Your folder structure should resemble the following:

src/
└── Calendar/
    ├── Calendar.tsx
    └── style.css

Enter fullscreen mode Exit fullscreen mode

Installing Day.js

As you already know, we will be using Day.js library for date manipulation, I recommend visiting their website to familiarize yourself with its capabilities. To install Day.js for our project, open your terminal and execute the following command:

npm install dayjs
Enter fullscreen mode Exit fullscreen mode

Before proceeding, you might be wondering why we are choosing Day.js for date formatting instead of relying on the built-in Date object. The rationale behind using a third-party library like Day.js includes several advantages, one of which is the fact that Day.js gives simple APIs for date manipulation. I will show this with an example.

Imagine you have to get a date in this format DD.MM.YYYY HH:mm:sss. Can you try to achieve this on your own using the built-in Date object?

One possible solution is to write a function that accepts a date and returns it in the format we desire. Let’s do that below.


const formatGivenDate = (date) => {

  const day = date.getDate(); 

  const month = date.getMonth() + 1; // Add 1 to month because it is zero based

  const year = date.getFullYear();

  const hours = date.getHours(); 

  const minutes = date.getMinutes();

  const seconds = date.getSeconds();

 // join with template literal and return formatted date

 return `${day < 10 ? "0" + day : day}.${month < 10 ? '0' + month : month}.${year} ${hours}:${minutes}:${seconds}`

}

const date = new Date();

console.log(formatGivenDate(date)) // 15.06.2024 21:48:13

Enter fullscreen mode Exit fullscreen mode

That looks quite straightforward, right? But what if we can achieve the same thing with a lot less than that?

Let us see what it looks like with Day.js.

const date = dayjs();

const formattedDate = date.format('DD.MM.YYYY HH:mm:ss')

Enter fullscreen mode Exit fullscreen mode

Yay, we got the same result in 2 lines of code.

Another reason why we are using Day.js is because it is a really lightweight library compared to a lot of the other available options — it is just 2kb in size.

Okay, back to building our calendar.

Starter files

To save time, I will provide you with the initial content of Calendar.tsx and the style.css files.


.calendar__container {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 25px;
  width: max-content;
  background: #ffffff;
  box-shadow: 5px 10px 10px #dedfe2;
}

.month-year__layout {
  display: flex;
  margin: 0 auto;
  width: 100%;
  flex-direction: row;
  align-items: center;
  justify-content: space-around;
}

.year__layout,
.month__layout {
  width: 150px;
  display: flex;
  padding: 10px;
  font-weight: 600;
  align-items: center;
  text-transform: capitalize;
  justify-content: space-between;
}

.back__arrow,
.forward__arrow {
  cursor: pointer;
  background: transparent;
  border: none;
}

.back__arrow:hover,
.forward__arrow:hover {
  scale: 1.1;
  transition: scale 0.3s;
}

.days {
  display: grid;
  grid-gap: 0;
  width: 100%;
  grid-template-columns: repeat(7, 1fr);
}

.day {
  flex: 1;
  font-size: 16px;
  padding: 5px 7px;
  text-align: center;
}

.calendar__content {
  position: relative;
  background-color: transparent;
}

.calendar__items-list {
  text-align: center;
  width: 100%;
  height: max-content;
  overflow: hidden;
  display: grid;
  grid-gap: 0;
  list-style-type: none;
  grid-template-columns: repeat(7, 1fr);
}

.calendar__items-list:focus {
  outline: none;
}

.calendar__day {
  position: relative;
  display: flex;
  justify-content: center;
  align-items: center;
}

.calendar__item {
  position: relative;
  width: 50px;
  height: 50px;
  cursor: pointer;
  background: transparent;
  border-collapse: collapse;
  background-color: white;

  display: flex;
  justify-content: center;
  align-items: center;
  text-align: center;
  border: 1px solid transparent;
  z-index: 200;
}

button {
  margin: 0;
  display: inline;
  box-sizing: border-box;
}

.calendar__item:focus {
  outline: none;
}

.calendar__item.selected {
  font-weight: 700;
  border-radius: 50%;
  background: #1a73e8;
  color: white;
  outline: none;
  border: none;
}

.calendar__item.selectDay {
  position: relative;
  background: #1a73e8;
  color: white;
  border-radius: 50%;
  border: none;
  z-index: 200;
}

.calendar__item.gray,
.calendar__item.gray:hover {
  color: #c4cee5;
  display: flex;
  justify-content: center;
  align-items: center;
}

.input__container {
  display: flex;
  justify-content: space-around;
}

.input {
  height: 30px;
  border-radius: 8px;
  text-align: center;
  align-self: center;
  border: 1px solid #1a73e8;
}

.shadow {
  position: absolute;
  display: inline-block;
  z-index: 10;
  top: 0;
  background-color: #f4f6fa;
  height: 50px;
  width: 50px;
}

.shadow.right {
  left: 50%;
}

.shadow.left {
  right: 50%;
}

Enter fullscreen mode Exit fullscreen mode
//Calendar.tsx

import backArrow from '../assets/images/back.svg';
import forwardArrow from '../assets/images/forward.svg';
import './style.css';

const weekDays = ['Sun', 'Mon', 'Tue', 'Wed', 'Thur', 'Fri', 'Sat'];

const daysListGenerator = {
  day: 15,
  prevMonthDays: [26, 27, 28, 29, 30],
  days: [
    1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
    22, 23, 24, 25, 26, 27, 28, 29, 30,
  ],
  remainingDays: [1, 2, 3, 4, 5],
};

export const Calendar = () => {
  return (
    <div className='calendar__container'>
      <div className='control__layer'>
        <div className='month-year__layout'>
          <div className='year__layout'>
            <button className='back__arrow'>
              <img src={backArrow} alt='back arrow' />
            </button>
            <div className='title'>2024</div>
            <button className='forward__arrow'>
              <img src={forwardArrow} alt='forward arrow' />
            </button>
          </div>
          <div className='month__layout'>
            <button className='back__arrow'>
              <img src={backArrow} alt='back arrow' />
            </button>
            <div className='new-title'>June</div>
            <button className='forward__arrow'>
              <img src={forwardArrow} alt='forward arrow' />
            </button>
          </div>
        </div>
        <div className='days'>
          {weekDays.map((el, index) => (
            <div key={`${el}-${index}`} className='day'>
              {el}
            </div>
          ))}
        </div>
        <div className='calendar__content'>
          <div className={'calendar__items-list'}>
            {daysListGenerator.prevMonthDays.map((el, index) => {
              return (
              <div key={`${el}/${index}`} className='calendar__day'>
                <button className='calendar__item gray'>
                  {el}
                </button>
              </div>
              );
            })}
            {daysListGenerator.days.map((el, index) => {
              return (
                <div key={`${index}-/-${el}`} className='calendar__day'>
                  <button
                    className={`calendar__item 
                      ${+el === +daysListGenerator.day ? 'selected' : ''}`}
                  >
                    <div className='day__layout'>
                      <div className='text'>{el.toString()}</div>
                    </div>
                  </button>
                </div>
              );
            })}

            {daysListGenerator.remainingDays.map((el, idx) => {
              return (
              <div key={`${idx}----${el}`} className='calendar__day'>
                <button className='calendar__item gray' >
                  {el}
                </button>
              </div>
              );
            })}
          </div>
        </div>
      </div>
    </div>
  );
};

Enter fullscreen mode Exit fullscreen mode
// src/App.tsx

import { Calendar } from './Calendar/Calendar';

function App() {
  return (
    <>
      <Calendar />
    </>
  );
}

export default App;

Enter fullscreen mode Exit fullscreen mode

You probably have noticed that I imported some svg files in the Calendar component, right?

I got them from svg repo but don’t worry I won’t leave you to source for them yourself. Here you have it.

//back.svg
<?xml version="1.0" encoding="utf-8"?>
<svg width="20px" height="20px" viewBox="0 0 1000 1000" class="icon"  version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M768 903.232l-50.432 56.768L256 512l461.568-448 50.432 56.768L364.928 512z" fill="#000000" /></svg>
Enter fullscreen mode Exit fullscreen mode
//forward.svg
<?xml version="1.0" encoding="utf-8"?>
<svg width="20px" height="20px" viewBox="0 0 1000 1000" class="icon"  version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M256 120.768L306.432 64 768 512l-461.568 448L256 903.232 659.072 512z" fill="#000000" /></svg>
Enter fullscreen mode Exit fullscreen mode

For no particular reason — other than just following along — create an image folder inside your assets directory and put the two svg files in this folder.

At this point, your file structure should look like this.

src/
└── assets/
     └──images/
         ├── back.svg
         └── forward.svg

└── Calendar/
    ├── Calendar.tsx
    └── style.css
Enter fullscreen mode Exit fullscreen mode

The daysListGenerator object in the Calendar.tsx file holds the values needed to populate our calendar. Let us look at what each of the property in the object means:

day is the current day or today. In the picture below, we can see it is 15.

prevMonthDays array contains the days in the previous month that will show in the current month. These days are so important as they will also help to offset the days in the current month and ensure that the first day of the current month matches with the corresponding day of the week. In the picture below these days are: [26, 27, 28, 29, 30]

days array contains the days in the current month, the length of this array will range from 28 to 31.

remainingDays array contains the days in the next month that will show in the current month. Since we have prevMonthDays, we can as well just have the remaining days as well. In the picture below, the remaining days are: [1, 2, 3, 4, 5, 6, 7]

Provided you have followed everything up to this point, if you start your application, you should have a calendar that looks like this:

Initial Calendar

The content of our calendar is currently hardcoded,so we need to fix this and make it as dynamic as we can.

Adding functionalities to the calendar.

So far, we have successfully initialized our project, installed DayJs, and hardcoded a basic calendar. Now, the next step is to ensure everything functions correctly. Our calendar should be able to display different months and years, as well as the corresponding days. Let’s focus on implementing these features to ensure our calendar is fully operational and not perpetually stuck in the past.

You would agree with me that in order to achieve our aim, all we need to do is ensure that the daysListGenerator object is dynamic and each of the properties change with respect to today’s date.

//

const daysListGenerator = {
  day: 15,
  prevMonthDays: [26, 27, 28, 29, 30],
  days: [
    1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
    22, 23, 24, 25, 26, 27, 28, 29, 30,
  ],
  remainingDays: [1, 2, 3, 4, 5],
};

//
Enter fullscreen mode Exit fullscreen mode

So, instead of hardcoding the properties in this object, we will try to generate them based on our current date (or today’s date).

To do that, let us create a helper function. This function will be responsible for generating all the currently hardcoded values in our calendar, such as day, prevMonthDays, days, and remainingDays.

In your src directory, create a folder called helper , inside this folder, create a calendarObjectGenerator.ts file. Your file structure should now look similar to this:

src/
└── assets/
     └──images/
         ├── back.svg
         └── forward.svg

└── Calendar/
    ├── Calendar.tsx
    └── style.css

└── helper   
     └── calendarObjectGenerator.ts
Enter fullscreen mode Exit fullscreen mode

Inside the calendarObjectGenerator.ts file, we will create a function called calendarObjectGenerator . This function will accept the currentDate or (today’s date), and will return an object containing calculated values, based on the current date, for all the properties we previously hardcoded.

This function is provided below:

//calendarObjectGenerator.ts

import dayjs, { Dayjs } from 'dayjs';
import isLeapYear from 'dayjs/plugin/isLeapYear';
import LocaleData from 'dayjs/plugin/localeData';
import customParseFormat from 'dayjs/plugin/customParseFormat';

dayjs.extend(customParseFormat);
dayjs.extend(LocaleData);

type GeneratedObjectType =  {
  days: number[];
  day: number;
  prevMonthDays: number[];
  remainingDays: number[];
  months: string[];
};


export const calendarObjectGenerator = (currentDate: Dayjs): GeneratedObjectType => {

  const numOfDaysInPrevMonth =  currentDate.subtract(1, 'month').daysInMonth();
  const firstDayOfCurrentMonth = currentDate.startOf('month').day()

  return {
  days: Array.from({ length: currentDate.daysInMonth() }, (_, index) => index + 1),
  day: Number(currentDate.format('DD')),
  months: currentDate.localeData().months(),

  //read explanation
  prevMonthDays: Array.from({length:firstDayOfCurrentMonth}, (_,index) => numOfDaysInPrevMonth - index).reverse(),

  remainingDays:  Array.from(
      { length: 6 - currentDate.endOf('month').day() },
      (_, index) => index + 1
    ),
}
Enter fullscreen mode Exit fullscreen mode

Let me explain how the values for each of the property in the object is calculated.

days: Array.from({length: currentDate.daysInMonth()}, (_,index) => index + 1)

To get the number of days in a month, we can use the daysInMonth() method from the DayJs object. This method returns the number of days as a numerical value (e.g 30). We can then create an array of integers from 1 to the number of days in currentDate.daysInMonth().

day: Number(currentDate.format('DD'))

We can extract the day of the month from our Day.js object by calling format method on the object and specifying what we want to extract.

months: currentDate.localeData().months()

This returns an array of all the months of the year.

prevMonthDays: Array.from({length:firstDayOfCurrentMonth}, (_,index) => numOfDaysInPrevMonth - index).reverse()

Now, let's consider the days from the previous month that we want to display. It's important to include these days as they will serve as an offset for the beginning of our current month, ensuring that the first day of the current month falls on the correct day of the week. Although getting the previous and remaining days can be a bit tricky, we can figure it out with some logic.

Let's break it down:

  • The method dayjs().startOf('month').day() gives us the first day of the current month, where 0 = Sunday and 6 = Saturday.

  • Using this value, we can create an array representing the number of days before the first day of the current month: Array.from({ length: dayjs().startOf('month').day() }).

  • DayJs makes it easy to find the last day of the previous month. Simply subtract one month from the current month and get the number of days: dayjs().subtract(1, 'month').daysInMonth().

  • Putting it all together, we can generate the last few days of the previous month: Array.from({ length: dayjs().startOf('month').day() }, (_, index) => numOfDaysInPrevMonth - index).

  • Finally, reverse the array to sort the days in increasing order.

remainingDays: Array.from( { length: 6 - currentDate.endOf('month').day() }, (_, index) => index + 1 )

To fill in the remaining days of our calendar, we can use one of two approaches:

  • Subtract from the Last Day of the Month:

    • Subtract the value of the last day of the month (0 = Sunday to 6 = Saturday) from 6.
    • Using this result, create an array of numbers in increasing order.
  • Total Days in the Calendar:

    remainingDays: 42 - (firstDayOfCurrentMonth + currentDate.daysInMonth())

    • Determine the total number of days you want to display in the calendar, for example, 42.
    • Calculate the remaining days by subtracting the sum of the total offset and the total number of days in the current month from 42.
    • If you're unsure why 42 is used, count the number of days in the hardcoded calendar above. You can choose the total number of days you want to display on your calendar.

Updating the days of the month.

Now that we have completed the calendarObjectGenerator function, let's put it to use to make our calendar dynamic.

Create a state to hold the current date:

const [currentDate, setCurrentDate] = useState<Dayjs>(dayjs(Date.now()));

Import calendarObjectGenerator into the Calendar.tsx file and replace the hardcoded object assinged to the daysListGenerator variable with calendarObjectGenerator(currentDate).

import dayjs, { Dayjs } from 'dayjs';
import backArrow from '../assets/images/back.svg';
import forwardArrow from '../assets/images/forward.svg';
import dayjs, { Dayjs } from 'dayjs';
import { calendarObjectGenerator } from '../helper/calendarObjectGenerator'; // add this line
import './style.css';

const weekDays = ['Sun', 'Mon', 'Tue', 'Wed', 'Thur', 'Fri', 'Sat'];

 export const Calendar = () => {

 const [currentDate, setCurrentDate] = useState<Dayjs>(dayjs(Date.now())); // add line

 const daysListGenerator = calendarObjectGenerator(currentDate); //add this line




 ... // other lines are the same as the content of Calendar.tsx above
 }
Enter fullscreen mode Exit fullscreen mode

Now your calendar should be updated and match the current month.

Enable click on month and year control arrows.

Even though, we have partly ensured that the calendar is not hardcoded anymore, and we will not get stuck in the past, there is still no way to peep into the future or travel back in time.

So our next challenge is to ensure we can go into the previous and future months and years.

That means we have to activate our arrows to respond to clicks.

Let’s create a function that will handle clicks on our arrows. We will call it dateArrowHandler. We will then use this function to control our months and years.

import dayjs, { Dayjs } from 'dayjs';
import backArrow from '../assets/images/back.svg';
import forwardArrow from '../assets/images/forward.svg';
import dayjs, { Dayjs } from 'dayjs';
import { calendarObjectGenerator } from '../helper/calendarObjectGenerator'; 
import './style.css';

const weekDays = ['Sun', 'Mon', 'Tue', 'Wed', 'Thur', 'Fri', 'Sat'];

 export const Calendar = () => {

 const [currentDate, setCurrentDate] = useState<Dayjs>(dayjs(Date.now()));

 const daysListGenerator = calendarObjectGenerator(currentDate);

 //add this function

  const dateArrowHandler = (date:Dayjs) => {
    setCurrentDate(date);
  };

  return ( 
  <div className='calendar__container'>
      <div className='control__layer'>
        <div className='month-year__layout'>
          <div className='year__layout'>
            <button
              className='back__arrow'
 //add line   onClick={() => dateArrowHandler(currentDate.subtract(1, 'year'))} 
            >
              <img src={backArrow} alt='back arrow' />
            </button>
 //add line <div className='title'>{currentDate.year()}</div>
            <button
              className='forward__arrow'
 //add line   onClick={() => dateArrowHandler(currentDate.add(1, 'year'))}
            >
              <img src={forwardArrow} alt='forward arrow' />
            </button>
           </div>

 ... // other lines are the same as the content of Calendar.tsx above
 }
Enter fullscreen mode Exit fullscreen mode

If you have been following, try making the months dynamic on your own before proceeding to the solution.

import dayjs, { Dayjs } from 'dayjs';
import backArrow from '../assets/images/back.svg';
import forwardArrow from '../assets/images/forward.svg';
import dayjs, { Dayjs } from 'dayjs';
import { calendarObjectGenerator } from '../helper/calendarObjectGenerator'; 
import './style.css';

const weekDays = ['Sun', 'Mon', 'Tue', 'Wed', 'Thur', 'Fri', 'Sat'];

 export const Calendar = () => {

 const [currentDate, setCurrentDate] = useState<Dayjs>(dayjs(Date.now()));

 const daysListGenerator = calendarObjectGenerator(currentDate);

 //add this function

  const dateArrowHandler = (date:Dayjs) => {
    setCurrentDate(date);
  };

  return ( 
  <div className='calendar__container'>
      <div className='control__layer'>
        <div className='month-year__layout'>
          <div className='year__layout'>
            <button
              className='back__arrow'
 //add line   onClick={() => dateArrowHandler(currentDate.subtract(1, 'year'))} 
            >
              <img src={backArrow} alt='back arrow' />
            </button>
 //add line <div className='title'>{currentDate.year()}</div>
            <button
              className='forward__arrow'
 //add line   onClick={() => dateArrowHandler(currentDate.add(1, 'year'))}
            >
              <img src={forwardArrow} alt='forward arrow' />
            </button>
          </div>
           <div className='month__layout'>
            <button
              className='back__arrow'
 //add line   onClick={() => dateArrowHandler(currentDate.subtract(1, 'month'))}
            >
              <img src={backArrow} alt='back arrow' />
            </button>
            <div className='new-title'>
 //add line  {daysListGenerator.months[currentDate.month()]}
            </div>
            <button
              className='forward__arrow'
 //add line   onClick={() => dateArrowHandler(currentDate.add(1, 'month'))}
            >
              <img src={forwardArrow} alt='forward arrow' />
            </button>
          </div>

 ... // other lines are the same as the content of Calendar.tsx above
 }
Enter fullscreen mode Exit fullscreen mode

Enable click on days.

Finally we want to allow the user to click on the days (numbers) on our calendar. We will be adding three main functions for this purspose. One for handling clicks on the prevMonthDays, the second for handling clicks on the days of the current month and the last one for handling clicks on the remainingDays.

...

 const handlePreviousMonthClick = (day: number) => {
    const dayInPreviousMonth = currentDate.subtract(1, 'month').date(day);
    setCurrentDate(dayInPreviousMonth);
  };

  const handleCurrentMonthClick = (day: number) => {
    const dayInCurrentMonth = currentDate.date(day);
    setCurrentDate(dayInCurrentMonth);

  };

  const handleNextMonthClick = (day: number) => {
    const dayInNextMonth = currentDate.add(1, 'month').date(day);
    setCurrentDate(dayInNextMonth);

  };


 ...
Enter fullscreen mode Exit fullscreen mode

As a little task for you, can you figure out where to put these functions in your code to enable clicks on the various days? Try to do that on your own. And once you are done, ensure your calendar looks somewhat like this:

The first calendar

That's all we need to do for this first part. I have provided the files containing everything we have written so far.

We will build upon this calendar in the subsequent parts. See you in the next one.

Summary.

Below is the whole code for what we have so far:

The complete css file is at the beginning.


// src/Calendar/Calendar.tsx

import dayjs, { Dayjs } from 'dayjs';
import backArrow from '../assets/images/back.svg';
import forwardArrow from '../assets/images/forward.svg';
import './style.css';
import { useState } from 'react';
import { calendarObjectGenerator } from '../helper/calendarObjectGenerator';

const weekDays = ['Sun', 'Mon', 'Tue', 'Wed', 'Thur', 'Fri', 'Sat'];

export const Calendar = () => {
  const [currentDate, setCurrentDate] = useState<Dayjs>(dayjs(Date.now()));

  const daysListGenerator = calendarObjectGenerator(currentDate);

  const dateArrowHandler = (date: Dayjs) => {
    setCurrentDate(date);
  };

  const handlePreviousMonthClick = (day: number) => {
    const dayInPreviousMonth = currentDate.subtract(1, 'month').date(day);
    setCurrentDate(dayInPreviousMonth);
  };

  const handleCurrentMonthClick = (day: number) => {
    const dayInCurrentMonth = currentDate.date(day);
    setCurrentDate(dayInCurrentMonth);
  };

  const handleNextMonthClick = (day: number) => {
    const dayInNextMonth = currentDate.add(1, 'month').date(day);
    setCurrentDate(dayInNextMonth);
  };

  return (
    <div className='calendar__container'>
      <div className='control__layer'>
        <div className='month-year__layout'>
          <div className='year__layout'>
            <button
              className='back__arrow'
              onClick={() => dateArrowHandler(currentDate.subtract(1, 'year'))}
            >
              <img src={backArrow} alt='back arrow' />
            </button>
            <div className='title'>{currentDate.year()}</div>
            <button
              className='forward__arrow'
              onClick={() => dateArrowHandler(currentDate.add(1, 'year'))}
            >
              <img src={forwardArrow} alt='forward arrow' />
            </button>
          </div>
          <div className='month__layout'>
            <button
              className='back__arrow'
              onClick={() => dateArrowHandler(currentDate.subtract(1, 'month'))}
            >
              <img src={backArrow} alt='back arrow' />
            </button>
            <div className='new-title'>
              {daysListGenerator.months[currentDate.month()]}
            </div>
            <button
              className='forward__arrow'
              onClick={() => dateArrowHandler(currentDate.add(1, 'month'))}
            >
              <img src={forwardArrow} alt='forward arrow' />
            </button>
          </div>
        </div>
        <div className='days'>
          {weekDays.map((el, index) => (
            <div key={`${el}-${index}`} className='day'>
              {el}
            </div>
          ))}
        </div>
        <div className='calendar__content'>
          <div className={'calendar__items-list'}>
            {daysListGenerator.prevMonthDays.map((el, index) => {
              return (
                <button
                  key={`${el}/${index}`}
                  className='calendar__item gray'
                  onClick={() => handlePreviousMonthClick(el)}
                >
                  {el}
                </button>
              );
            })}
            {daysListGenerator.days.map((el, index) => {
              return (
                <div
                  key={`${index}-/-${el}`}
                  className='calendar__day'
                  onClick={() => handleCurrentMonthClick(el)}
                >
                  <button
                    className={`calendar__item 
                      ${+el === +daysListGenerator.day ? 'selected' : ''}`}
                  >
                    <div className='day__layout'>
                      <div className='text'>{el.toString()}</div>
                    </div>
                  </button>
                </div>
              );
            })}

            {daysListGenerator.remainingDays.map((el, idx) => {
              return (
                <button
                  className='calendar__item gray'
                  key={`${idx}----${el}`}
                  onClick={() => handleNextMonthClick(el)}
                >
                  {el}
                </button>
              );
            })}
          </div>
        </div>
      </div>
    </div>
  );
};


Enter fullscreen mode Exit fullscreen mode
// src/helper/calendarObjectGenerator.ts

import dayjs, { Dayjs } from 'dayjs';

import LocaleData from 'dayjs/plugin/localeData';

dayjs.extend(LocaleData);

type GeneratedObjectType = {
  prevMonthDays: number[];
  days: number[];
  remainingDays: number[];
  day: number;
  months: string[];
};

export const calendarObjectGenerator = (
  currentDate: Dayjs
): GeneratedObjectType => {
  const numOfDaysInPrevMonth = currentDate.subtract(1, 'month').daysInMonth();
  const firstDayOfCurrentMonth = currentDate.startOf('month').day();
  return {
    days: Array.from(
      { length: currentDate.daysInMonth() },
      (_, index) => index + 1
    ),
    day: Number(currentDate.format('DD')),
    months: currentDate.localeData().months(),

    prevMonthDays: Array.from(
      { length: firstDayOfCurrentMonth },
      (_, index) => numOfDaysInPrevMonth - index
    ).reverse(),

    remainingDays: Array.from(
      { length: 6 - currentDate.endOf('month').day() },
      (_, index) => index + 1
    ),
  };
};

Enter fullscreen mode Exit fullscreen mode

Top comments (0)