avatar
screenshot
2024年4月〜

zero-one-ui/calendar

カレンダー部分をフルスクラッチで書いたWebカレンダーです。ドラッグ&ドロップによるイベント作成や移動など、様々な機能を実装しています。

概要

zero-one-uiプロジェクトで作ったUIです。 Reactを使って、フルスクラッチでカレンダーを実装しています。

月間、週間、日間、指定した期間のカレンダーなどを実装しています。 それぞれのカレンダーで、ドラッグ&ドロップを使用してイベントを作成したり、イベントの期間を変更したり、イベントを別の日時に移動することができます。 画面左の小さいカレンダーから、カレンダーに表示する期間を変更することもできます。

ブラウザ上で動作するカレンダーの実装方法に興味があったため、実際に作ってみました。

使用した技術と実装の詳細

  • TypeScript
  • React
  • date-fns
  • Tailwind CSS
  • Floating UI

カレンダーの実装

様々な形式のカレンダーを表示することができるのですが、どれもカレンダー行とカレンダー列の2つで構成されおり、 それぞれの要素の中に、行イベントや列イベントなどが表示されます。 カレンダー行とカレンダー列に分けることで、柔軟にカレンダーを構築することが可能になっています。

  • カレンダー行
    • 月間カレンダーの1行を構成するもので、1行に複数の日付が含まれています。
  • カレンダー列
    • 週間カレンダーの1列を構成するもので、1列に複数の時刻が含まれています。

週間、日間、指定した期間のカレンダーは、カレンダー列を日数分並べたものになります。 また、カレンダー列を並べて表示されるカレンダーでは、複数の日にまたがるイベントは、 カレンダー上部の1行だけ表示されているカレンダー行に表示されます。

日付の操作

カレンダーを作っていると、連続した日付や連続した時刻が必要になったり、特定の日付が指定した期間に含まれているかを判断したくなることが結構あります。 日付を操作するためのライブラリであるdate-fnsには、そういった操作のための関数が豊富に含まれていたので、ほとんど自分で実装する必要がなく便利でした。 例えば、以下のようなコードで今日の連続した時刻を取得することができます。

ts
const date = new Date(); eachHourOfInterval({ start: startOfDay(date), end: endOfDay(date) });

カレンダーのイベント操作

カレンダーのイベントは、作成、期間の変更、移動などを行うことができます。 それぞれの操作を一つのhookにまとめることで、特定の操作のロジックの変更を、一つのファイルで行えるように工夫しています。

カレンダーのイベントは削除することができるのですが、一定時間内であれば取り消せるような実装を行っています。 はじめはtanstack-queryのキャッシュの中からイベントを削除するようにしていたのですが、refetchされると画面に表示されてしまうため、 グローバルな状態として削除しようとしているイベントのidを持たせて、 イベントのリストを取得するhookの内部で、削除しようとしているイベントを除外するような実装を行っています。

プロジェクトから学んだこと

関連するコードをまとめることで変更容易性を高めることができるというのは理解していましたが、 このプロジェクトを通して、それをより実感しました。 イベントは複数の操作が可能なのですが、初期の段階では、一つの操作のロジックが2つのコンポーネントに散らばっていたり、 各操作のロジックが一つのコンポーネントに混在していました。 そのため、区別するために変数名が冗長になったり、変更が必要な際に適切な修正箇所を特定するのが難しくなるなどの問題が発生していました。 それぞれの操作をhookにまとめることで、そういった問題を解決することができました。