Overview

  • How to add an interactive open-source calendar component to your Next.js application.

  • The calendar component supports drag and drop, CSS themes, event content customization and other features.

  • Built using Next.js 14 and the React package of the open-source DayPilot Lite for JavaScript library.

License

Apache License 2.0

Create a New Next.js Application

First, create a new Next.js application using create-next-app:

npx create-next-app

Install the React Calendar Component

Use npm to install the DayPilot Lite for React package (@daypilot/daypilot-lite-react):

npm install @daypilot/daypilot-lite-react

DayPilot Lite is an open-source calendar and scheduling library, which includes a React Calendar component. We will use this component to create a drag-and-drop scheduling calendar in our Next.js application.

Main component

The main component of the Next.js application (Home) is a very simple React component that displays the Calendar, which we define in the next step.

page.tsx

import Calendar from "@/app/Calendar";

export default function Home() {
  return (
      <div>
        <Calendar/>
      </div>
  )
}

React Calendar Component in Next.js

React Calendar Component in Next.js

Our Next.js Calendar component will display a weekly calendar using the open-source React calendar component from the DayPilot Lite library.

DayPilot Calendar is an interactive client-side component but you can use it in server-side rendered (SSR) Next.js React applications as well. To make this work, it’s necessary to mark the component using 'use client' at the top of the source file.

The component includes standard configuration that you know from the React Weekly Calendar tutorial:

1. It defines the Calendar configuration using a special state variable (config):

  • The calendar is switched to a week view (viewType: "Week").

  • It will display a week that includes the startDate.

  • To resolve the first day of week, it uses the specified locale.

2. It stores the DayPilot.Calendar object reference in a calendar state variable.

  • By default, the calendar state variable value is undefined.

  • The value will be set using the controlRef attribute of the <DayPilotCalendar> JSX tag when the component is mounted.

  • The calendar variable lets you access the direct API. As an example, we will use the direct API to load the calendar event data.

3. Our Next.js app loads the calendar events in a useEffect() block.

  • The useEffect() callback uses the calendar variable as a dependency. It ensures that it will be executed when the calendar value is set.

  • The event data are defined as a static array. Normally you will use an HTTP request to a JSON endpoint instead.

calendar.tsx

'use client';

import React, {useEffect, useState} from "react";
import {DayPilot, DayPilotCalendar} from "@daypilot/daypilot-lite-react";

export default function Calendar() {

    const [calendar, setCalendar] = useState<DayPilot.Calendar>();

    const initialConfig: DayPilot.CalendarConfig = {
        viewType: "Week",
        startDate" "2024-10-01",
        locale: "en-us"
    };

    const [config, setConfig] = useState(initialConfig);

    useEffect(() => {

        if (!calendar || calendar?.disposed()) {
            return;
        }
        const events: DayPilot.EventData[] = [
            {
                id: 1,
                text: "Event 1",
                start: "2024-10-02T10:30:00",
                end: "2024-10-02T13:00:00",
                tags: {
                    participants: 2,
                }
            },
            
            // ...
            
        ];

        calendar.update({events});
    }, [calendar]);


    return (
        <div>
            <DayPilotCalendar
                {...config}
                controlRef={setCalendar}
            />
        </div>
    )
}

Show Number of Participants as an Icon in Next.js Calendar Events

Show Number of Participants as an Icon in Next.js Calendar Events

As an example of the customization capabilities of the Calendar component, we will change the appearance of the calendar events using the onBeforeEventRender event handler.

First, add the onBeforeEventRender attribute to the <DayPilotCalendar> tag. It will point to an onBeforeEventRender function that we define below.

JSX:

<DayPilotCalendar
    {...config}
    onBeforeEventRender={onBeforeEventRender}
    controlRef={setCalendar}
/>

The event handler will be fired once for every visible event.

It lets you modify the appearance and behavior of the calendar events. Our example adds an icon with a number of participants to the lower-left corner of each event.

1. The number of participants is defined using a participants property of the tags field of the event data object:

{
  id: 1,
  text: "Event 1",
  start: "2024-10-02T10:30:00",
  end: "2024-10-02T13:00:00",
  tags: {
      participants: 2,
  }
},

2. In the event handler, we will read the value and add the icons if there the participants are defined:

const participants = args.data.tags?.participants || 0;

Full event handler code:

const onBeforeEventRender = (args: DayPilot.CalendarBeforeEventRenderArgs) => {

    // ...

    const participants = args.data.tags?.participants || 0;
    if (participants > 0) {
        args.data.areas.push({
            bottom: 5,
            left: 5,
            width: 24,
            height: 24,
            action: "None",
            backColor: "#00000033",
            fontColor: "#fff",
            text: participants,
            style: "border-radius: 50%; border: 2px solid #fff; font-size: 18px; text-align: center;",
        });
    }
};

Full Source Code

Here is the full source code of our Calendar component:

'use client';

import React, {useEffect, useState} from "react";
import {DayPilot, DayPilotCalendar} from "@daypilot/daypilot-lite-react";

export default function Calendar() {

    const styles = {
        wrap: {
            display: "flex"
        },
        left: {
            marginRight: "10px"
        },
        main: {
            flexGrow: "1"
        }
    };

    const colors = [
        {name: "Green", id: "#6aa84f"},
        {name: "Blue", id: "#3d85c6"},
        {name: "Turquoise", id: "#00aba9"},
        {name: "Light Blue", id: "#56c5ff"},
        {name: "Yellow", id: "#f1c232"},
        {name: "Orange", id: "#e69138"},
        {name: "Red", id: "#cc4125"},
        {name: "Light Red", id: "#ff0000"},
        {name: "Purple", id: "#af8ee5"},
    ];

    const participants = [
        {name: "1", id: 1},
        {name: "2", id: 2},
        {name: "3", id: 3},
        {name: "4", id: 4},
    ];

    const [calendar, setCalendar] = useState<DayPilot.Calendar>();

    const editEvent = async (e: DayPilot.Event) => {
        const form = [
            {name: "Event text", id: "text", type: "text"},
            {name: "Event color", id: "backColor", type: "select", options: colors},
            {name: "Number of participants", id: "tags.participants", type: "select", options: participants},
        ];

        const modal = await DayPilot.Modal.form(form, e.data);
        if (modal.canceled) { return; }
        e.data.text = modal.result.text;
        e.data.backColor = modal.result.backColor;
        e.data.tags.participants = modal.result.tags.participants;
        calendar?.events.update(e);
    };

    const contextMenu = new DayPilot.Menu({
        items: [
            {
                text: "Delete",
                onClick: async args => {
                    calendar?.events.remove(args.source);
                },
            },
            {
                text: "-"
            },
            {
                text: "Edit...",
                onClick: async args => {
                    await editEvent(args.source);
                }
            }
        ]
    });

    const onBeforeEventRender = (args: DayPilot.CalendarBeforeEventRenderArgs) => {
        args.data.areas = [
            {
                top: 5,
                right: 5,
                width: 20,
                height: 20,
                symbol: "icons/daypilot.svg#minichevron-down-2",
                fontColor: "#fff",
                backColor: "#00000033",
                style: "border-radius: 25%; cursor: pointer;",
                toolTip: "Show context menu",
                action: "ContextMenu",
            },
        ];


        const participants = args.data.tags?.participants || 0;
        if (participants > 0) {
            args.data.areas.push({
                bottom: 5,
                left: 5,
                width: 24,
                height: 24,
                action: "None",
                backColor: "#00000033",
                fontColor: "#fff",
                text: participants,
                style: "border-radius: 50%; border: 2px solid #fff; font-size: 18px; text-align: center;",
            });
        }
    };

    const initialConfig: DayPilot.CalendarConfig = {
        viewType: "Week",
        durationBarVisible: false,
    };

    const [config, setConfig] = useState(initialConfig);

    useEffect(() => {

        if (!calendar || calendar?.disposed()) {
            return;
        }
        const events: DayPilot.EventData[] = [
            {
                id: 1,
                text: "Event 1",
                start: "2024-10-02T10:30:00",
                end: "2024-10-02T13:00:00",
                tags: {
                    participants: 2,
                }
            },
            {
                id: 2,
                text: "Event 2",
                start: "2024-10-03T09:30:00",
                end: "2024-10-03T11:30:00",
                backColor: "#6aa84f",
                tags: {
                    participants: 1,
                }
            },
            {
                id: 3,
                text: "Event 3",
                start: "2024-10-03T12:00:00",
                end: "2024-10-03T15:00:00",
                backColor: "#f1c232",
                tags: {
                    participants: 3,
                }
            },
            {
                id: 4,
                text: "Event 4",
                start: "2024-10-01T11:30:00",
                end: "2024-10-01T14:30:00",
                backColor: "#cc4125",
                tags: {
                    participants: 2,
                }
            },
        ];

        const startDate = "2024-10-01";

        calendar.update({startDate, events});
    }, [calendar]);

    const onTimeRangeSelected = async (args: DayPilot.CalendarTimeRangeSelectedArgs) => {
        const modal = await DayPilot.Modal.prompt("Create a new event:", "Event 1");
        calendar?.clearSelection();
        if (modal.canceled) {
            return;
        }
        // console.log("modal.result", modal.result, calendar);
        calendar?.events.add({
            start: args.start,
            end: args.end,
            id: DayPilot.guid(),
            text: modal.result,
            tags: {
                participants: 1,
            }
        });
    };

    return (
        <div>
            <DayPilotCalendar
                {...config}
                onTimeRangeSelected={onTimeRangeSelected}
                onEventClick={async args => { await editEvent(args.e); }}
                contextMenu={contextMenu}
                onBeforeEventRender={onBeforeEventRender}
                controlRef={setCalendar}
            />
        </div>
    )
}

package.json

This is the package.json file used in the attached project. It includes dependencies required for a Next.js 14 application and the open-source version of DayPilot.

{
  "name": "nextjs-calendar-open-source",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  },
  "dependencies": {
    "@daypilot/daypilot-lite-react": "^3.20.0",
    "next": "14.0.4",
    "react": "^18",
    "react-dom": "^18"
  },
  "devDependencies": {
    "@types/node": "^20",
    "@types/react": "^18",
    "@types/react-dom": "^18",
    "eslint": "^8",
    "eslint-config-next": "14.0.4",
    "typescript": "^5"
  }
}