FullCalendarでQiita Advent Calendarみたいなカレンダーを作ってみた！


### 目次

[1.はじめに](#1.-はじめに)
[2.FullCalendarとは](#2.-FullCalendarとは)
[3.今回作るもの](#3.-今回作るもの)
[5. まとめ](#5.-まとめ)


# はじめに

今回は、FullCalendarライブラリを使って、Qiita Advent Calendarみたいなカレンダーを作りながら、FullCalendarのカスタマイズ方法を紹介していきます！

### この記事でわかる・できること

> - FullCalendarでのカレンダー表示
> - FullCalendarのスタイルのカスタマイズ方法
> - FullCalendarの機能のカスタマイズ方法

### 対象者

> - Next.jsでFullCalendarを触ってみたい方

###  背景

業務で何度かFullCalendarを使用する機会があり、そのカスタマイズの柔軟性と便利な機能の多さに驚きました。できることが豊富で、さまざまなニーズに対応できる点が非常に魅力的です。
また、FullCalendarの公式ドキュメントは非常に見やすく整理されており、初めて扱う方でもスムーズに導入できると思います。

# FullCalendarとは

javascriptで作られたオープンソースのカレンダーライブラリのことです。
公式：https://fullcalendar.io

![スクリーンショット 2024-12-19 17.44.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3619984/69780656-6eee-e12d-661e-69ed02193668.png)

# 今回作るもの

今回は、Qiita Advent Calendarのデザインや機能をまねてカレンダーを作っていきます。
完成系はこんな感じ！

### 環境
> - Next.js

# Qiita Advent Calendarを作ってみよう！！

## FullCalendarの導入

下記のコマンドを使って、FullCalendarをインストールします。
```
npm install fullcalendar
```

## カレンダーの表示

早速カレンダーを表示していきます。
インストール手順、初期表示の方法については、公式ドキュメントで丁寧に説明されています。
公式：[FullCalendarの導入と初期表示](https://fullcalendar.io/docs/react)

カレンダーの表示タイプは様々ですが、
今回は、月表示のカレンダーにしたいので、`daygrid`を選択します。


```typescript
'use client';

import FullCalendar from '@fullcalendar/react'
import dayGridPlugin from '@fullcalendar/daygrid' // a plugin!

export default function Calendar() {
  return (
    <div className="w-1/2 m-auto">
        <FullCalendar
          plugins={[ dayGridPlugin ]}
          initialView="dayGridMonth"
          width={700}
        />
    </div>
  )
}
```

それでは、ブラウザで確認してみましょう。

![スクリーンショット 2024-12-20 21.23.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3619984/5b63b065-4fc1-ced2-77c4-b2c53bb4d711.png)


すごいですよね。いとも簡単にカレンダーが表示できてしまいました。
しかし、FullCalendarの醍醐味は、カスタマイズの幅の広さにあると思っています。
次は、スタイルのカスタマイズに移っていきましょう。

## スタイルのカスタマイズ

では、Qiita Advent Calendarのデザインに寄せていきましょう！

Qiita Advent Calendarはどうなっているかというと・・・？
![スクリーンショット 2024-12-20 21.28.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3619984/64c69dca-40a3-4d22-6e59-e8451c14e070.png)

スタイルの調整内容
> - fullcalendarのデフォルト表示のようにヘッダーはない
> - 日本語表示
> - 各日付セルの左右の枠線がない
> - 今日の日付部分は赤色
> - 25日まで活性状態

上記の表示になるよう修正していきましょう。
下記のように、プロパティを追加します。
```typescript
'use client';

import FullCalendar from '@fullcalendar/react'
import dayGridPlugin from '@fullcalendar/daygrid' // a plugin!
// 日本語化
import jaLocale from '@fullcalendar/core/locales/ja'


export default function Calendar() {
  return (
    <div className="w-1/2 m-auto">
        <FullCalendar
        plugins={[ dayGridPlugin ]}
        initialView="dayGridMonth"
        locale={jaLocale} // 日本語化
        // カレンダーのヘッダー部分の設定
        headerToolbar={{
            left: '',
            center: '',
            right: '',
        }}
        // 日付のヘッダー
        dayCellContent={(arg) => {
            // `arg.date`には各日のDateオブジェクトが含まれます。
            const day = arg.date.getDate(); // 日付を取得 (1, 2, 3...)
            return <span>{day}</span>; // セルに数値の日付だけを表示
        }}
        // 日付の表示範囲の調整
        validRange={{
            start: '2024-12-01 00:00:00', // 表示開始日
            end: '2024-12-25 23:59:59',   // 表示終了日
        }}
        />
    </div>
  )
}
```

では、ブラウザで確認してみましょう
![スクリーンショット 2024-12-20 22.18.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3619984/b88f24e8-a3cc-17d0-9ddd-9ab7a0d72321.png)


ヘッダーを削除したり、日本語表示にしたりはできましたが、
左右の枠線を消すプロパティや今日の日付のスタイルを変更するプロパティは用意されていなようです・・・

このように細かなスタイルの調整には、CSSを直接上書きする必要があります。
CSSファイルを作成して、下記のように修正しましょう。
（スタイルを適用するようにしてください。）
```css
/* 「今日の日付」の色を変更 */
.fc .fc-daygrid-day.fc-day-today {
  background-color: #fff;
}

.fc .fc-daygrid-day.fc-day-today .fc-daygrid-day-top {
  background-color: #fddbdb;
}

/* カレンダーの左線の削除 */
.fc .fc-day{
  border-left: none;
  border-right: none;
}

/* カレンダーの左線の削除 */
.fc-theme-standard .fc-scrollgrid {
  border: none;
  border-top: 1px solid var(--fc-border-color);
}

/* カレンダーの右線・下線の削除 */
.fc .fc-scrollgrid-section-liquid > td {
  border: none;
}

/* 曜日ヘッダーの右枠の線削除 */
.fc .fc-scrollgrid-section-footer > *, .fc .fc-scrollgrid-section-header > * {
  border: none;
}

/* 各日付の左右の線を削除 */
.fc .fc-theme-standard .fc-scrollgrid {
  border-left: none;
}
```

さて、このクラス名はどこから取得しているかというと・・・
developerツールを開いて、スタイルを変えたい要素をフォーカスし、クラス名をコピーして使用します。地道ですが・・・


では、ブラウザで確認してみましょう。
![スクリーンショット 2024-12-21 2.10.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3619984/b6e02c46-4b2e-b335-89c8-69e5095fe460.png)


だんだんとQiita Advent Calendarに近づいてきたのではないでしょうか。
FullCalendarには、スタイル修正のためのプロパティが用意されていることもありますが、
やりたいことが全部できるわけではないので、細かいスタイル調整はCSSで行うことが多いです。

## 機能のカスタマイズ

最後に、カレンダーに機能をつけていきましょう。
Qiita Advent Calendarでは、記事の投稿予約がない日には「+」ボタンが表示され、クリックするとモーダルが開き、投稿予約が作成できる仕組みになっていますね。
逆に投稿予約のある日は、クリックすると記事に遷移するようになっています。

この3つの機能を作っていきましょう。
> - 記事投稿予約の表示
> - 「+」クリックでモーダルの表示
> - 記事投稿予約の作成

### カレンダーイベントの表示

カレンダー上に予定を表示するには、`event`プロパティにデータを入れる必要があります。
今回は、APIとの接続はないため静的なデータを定義し、表示します。
下記のように修正してください。
```typescript
...
...

export default function Calendar() {
  const event =[
        {
            'url': 'https://fullcalendar.io/', // URLを設定することで、予定をクリックするとリンク先に遷移する
            'title': "Qiita Advent CalendarみたいなカレンダーをFullCalendarで作ってみた！",
            'start': "2024-12-21"
        },
        {
            'title': "クリスマス",
            'start': "2024-12-24"
        }
    ]
  return (
    <div className="w-1/2 m-auto">
        <FullCalendar
            ...
            ...
            ...
            // 予定の表示
            events={event}
            eventTextColor={"#000"} // イベントのテキストカラーの調整
            eventColor={"#fff"}  // イベントのborder・backgroundのカラーの調整
        />
    </div>
  )
}
```

では、ブラウザで確認してみましょう。
予定が表示されていますね。
21日の予定をクリックすると、リンク先に遷移するので確認してみてください。
![スクリーンショット 2024-12-21 3.06.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3619984/990bda94-edca-18d5-4006-ba7777a6276b.png)

### 日付クリック時の処理

次に、日付クリック時にモーダルを開くようにしましょう。
日付クリック時にcallback関数を呼び出すようにするプロパティの
下記のように修正しましょう。
```typescript
...省略
import interactionPlugin from "@fullcalendar/interaction"; // 日付クリックに必要なプラグイン

  ...省略
  const [isOpenModal, setIsOpenModal] = useState(false);
  return (
    <div className="w-1/2 m-auto relative">
      <FullCalendar
        // dateClickを使用するのに必要な、interactionPluginを追加
        plugins={[dayGridPlugin, interactionPlugin]}
        ...省略
        // 日付クリックでモーダルを開く
        dateClick={() => {
          setIsOpenModal(true);
        }}
      />
      // モーダル
      {isOpenModal && (
        <div
          className="fixed top-0 left-0 w-full h-full bg-black bg-opacity-25 flex justify-center items-center z-10"
          onClick={() => setIsOpenModal(false)} // 背景をクリックすると閉じる
        >
          <div className="w-[400px] bg-white z-10 rounded-lg p-10 flex flex-col gap-4">
            <h3 className="font-bold">カレンダーの1日目に参加する</h3>
            <label className="block">タイトル</label>
            <input
              className="border p-2"
              type="text"
              placeholder="fullcalendaについての記事を書きます"
            />
            <label className="block">URL</label>
            <input
              className="border p-2"
              type="url"
              placeholder="http://qiita.com/"
            />
            <div className="flex gap-5">
              <button onClick={() => setIsOpenModal(false)}>キャンセル</button>
              <button>参加する</button>
            </div>
          </div>
        </div>
      )}
    </div>
  );
}

```
日付部分をクリックして、モーダルが表示されるか確認してみましょう。

![スクリーンショット 2024-12-21 5.05.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3619984/ff73f4be-8b23-3765-0e7b-fff79e03745f.png)

### イベントの作成

最後の最後に、モーダルに入力した情報をカレンダーのイベントとして作成していきます。
イベントを作成するには、FullCalendarのAPIである`addEvent`を使用します。
また、APIを取得するために、`useRef`を使用する必要があります。

モーダル内の、`input`、`button(参加する)`にonCheck属性・onClick属性を追加しています。
下記のように修正しましょう。
```typescript
...省略

export default function Calendar() {
  ... 省略
  
  // イベントに登録する要素たち
  const [title, setTitle] = useState("");
  const [url, setUrl] = useState("");
  const [date, setDate] = useState("");

  // カレンダーのAPIを取得するため、useRefを使用
  const calendarRef = useRef<FullCalendar>(null);

  // イベント作成処理
  const onSave = () => {
    if (!calendarRef.current) return;

    calendarRef.current.getApi().addEvent({
      title: title,
      start: date,
      url: url,
    });
    setIsOpenModal(false);
  };
  return (
    <div className="w-1/2 m-auto relative">
      <FullCalendar
        ref={calendarRef}
        ...省略
        // events={events} //作成したイベントが静的データに上書きされてしまうため一旦コメントアウト
        ...省略
        // 日付クリックでモーダルを開く
        dateClick={(arg: DateClickArg) => {
          setDate(arg.dateStr);
          setIsOpenModal(true);
        }}
      />
      {isOpenModal && (
          ...省略
            <input
              className="border p-2"
              type="url"
              placeholder="http://qiita.com/"
              // 変更ごとに、イベントのURLを保存
              onChange={(e) => setUrl(e.target.value)}
            />
            <div className="flex gap-5">
              <button onClick={() => setIsOpenModal(false)}>キャンセル</button>
              <button
                onClick={() => {
                  onSave();
                }}
              >
                参加する
              </button>
            </div>
          </div>
        </div>
      )}
    </div>
  );
}

```

完成です！これで、記事予約投稿の予定を作成することができました！！
![画面収録-2024-12-21-5.47.36.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3619984/bcad524d-2258-954b-0e5a-28a00e77efd3.gif)


# まとめ

かなりQiita Advent Calendarに似せたカレンダーを作成できたのではないでしょうか。
（もう少しデザインは寄せたかったですが・・・）
紹介した機能はまだまだ一部で、FullCalendarではもっとたくさんのことができます。
例えば、カレンダー表示のタイプをtimeGridにすると、GoogleCalendarのようにドラッグアンドドロップができるようなカレンダーを作成することができます。
実装していると、こんなことも、あんなこともできる！と、とても楽しいので、ぜひ触ってみていただければと思います。