7
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

FullCalendar React+TypeScript Example Projectを関数コンポーネントで書いてみる 03

Last updated at Posted at 2021-07-13

FullCalendar公式の「FullCalendar React+TypeScript Example Project」を関数コンポーネントで書き替えるチュートリアルの最終回は、カレンダーの左側にパネルを差し込み、さらに公式作例にない機能も加えます。でき上がりはCodeSandboxに公開したつぎのサンプル001でお確かめください。

サンプル001■React + TypeScript + FullCalendar: Example calendar 03

2107003_006.png
>> CodeSandboxへ

サイドバーを加える

カレンダーの左に差し込むパネルがサイドバーです。操作説明のテキストとチェックボックスをひとつ加えます。新たなモジュールsrc/Sidebar.tsxに分けて定めることにしましょう(公式「FullCalendar React+TypeScript Example Project」では分けていません)。チェックボックスは、週末(土日)の表示・非表示を切り替えます。

親のルートモジュールから受け取るふたつのプロパティは、チェックボックスのチェックを切り替えたときのonChangeコールバック(toggleWeekends)と、週末の表示・非表示を示す真偽値の状態変数(weekendsVisible)です。

src/Sidebar.tsx
interface Props {
  toggleWeekends: () => void;
  weekendsVisible: boolean;
}
const Sidebar: React.VFC<Props> = ({ toggleWeekends, weekendsVisible }) => (
  <div className="demo-app-sidebar">
    <div className="demo-app-sidebar-section">
      <h2>操作方法</h2>
      <ul>
        <li>日付を選んでイベント作成</li>
        <li>ドラッグ&amp;ドロップでイベントの長さ変更</li>
        <li>クリックでイベント削除</li>
      </ul>
    </div>
    <div className="demo-app-sidebar-section">
      <label>
        <input
          type="checkbox"
          checked={weekendsVisible}
          onChange={toggleWeekends}
        />
        週末を表示
      </label>
    </div>
  </div>
);

export default Sidebar;

週末を表示するかどうかは、FullCalendarコンポーネントのweekendsで切り替えます。デフォルト値は表示するtrueです。チェックを外すと、土日が隠れるようになりました(図001)。

src/App.tsx
import Sidebar from "./Sidebar";

function App() {

  const [weekendsVisible, setWeekendsVisible] = useState(true);

  const handleWeekendsToggle = useCallback(
    () => setWeekendsVisible(!weekendsVisible),
    [weekendsVisible]
  );
  return (
    <div className="demo-app">
      <Sidebar
        toggleWeekends={handleWeekendsToggle}
        weekendsVisible={weekendsVisible}
      />
      <div className="demo-app-main">
        <FullCalendar

          weekends={weekendsVisible}

        />
      </div>
    </div>
  );
}

図001■チェックボックスで週末の表示・非表示が切り替わる

2107003_001.png

サイドバーに予定一覧を加える

サイドバーに予定(イベント)を一覧表示しましょう。データはルートコンポーネントAppの状態変数currentEventsに配列で収めてありました。これをSidebarコンポーネントにプロパティとして渡しておきます。

src/App.tsx
function App() {

  return (
    <div className="demo-app">
      <Sidebar
        currentEvents={currentEvents}

      />

    </div>
  );
}

日付を適切な書式の文字列に整えるのが、formatDate関数です。第1引数に日付、第2引数には書式が定められたオブジェクトを渡してください(詳しくは「Date Formatting」参照)。なお、第1引数はnullでないことを型アサーションするために、nullアサーション演算子!が添えてあります。

src/Sidebar.tsx
import { EventApi, formatDate } from "@fullcalendar/react";

interface Props {
  currentEvents: EventApi[];

}
const renderSidebarEvent = (event: EventApi) => (
  <li key={event.id}>
    <b>
      {formatDate(event.start!, {
        year: "numeric",
        month: "short",
        day: "numeric",
        locale: "ja",
      })}
    </b>
    <i>{event.title}</i>
  </li>
);
// const Sidebar: React.VFC<Props> = ({ toggleWeekends, weekendsVisible }) => {
const Sidebar: React.VFC<Props> = ({
  currentEvents,

}) => (
  <div className="demo-app-sidebar">

    <div className="demo-app-sidebar-section">
      <h2>予定一覧({currentEvents.length})</h2>
      <ul>{currentEvents.map(renderSidebarEvent)}</ul>
    </div>
  </div>
);

これで、親コンポーネントの状態変数(currentEvents)の配列から予定データをすべて取り出して、サイドバーのコンポーネント(Sidebar)に一覧表示できるようになりました(図002)。

図002■予定一覧が加わった

2107003_002.png

予定リストのビューを加える

FullCalendarにビューをもうひとつ加えます。つぎのプラグインをインストールしてください。

npm install --save @fullcalendar/list

importしたプラグイン(listPlugin)は、FullCalendarコンポーネントのpluginsの配列に収めます。これは予定リストのビューです(図003)。ヘッダの右側ボタンにlistMonthとして加えましょう(「Docs / List View」参照)。公式「FullCalendar React+TypeScript Example Project」には、このビューが含まれていません(この予定リストがあれば、サイドバーの予定一覧はいらなくなりそうです)。

src/App.tsx
import listPlugin from "@fullcalendar/list";

function App() {

  return (
    <div className="demo-app">

      <div className="demo-app-main">
        <FullCalendar
          // plugins={[dayGridPlugin, timeGridPlugin, interactionPlugin]}
          plugins={[

            listPlugin,
          ]}
          headerToolbar={{

            // end: "dayGridMonth,timeGridWeek,timeGridDay",
            end: "dayGridMonth,timeGridWeek,timeGridDay,listMonth",
          }}

        />
      </div>
    </div>
  );
}

図003■予定リストのビューが加わった

2107003_003.png

日時の書式を変える

公式「FullCalendar React+TypeScript Example Project」にはない追加として、さらに日時の書式を変えてみます。まず、「12時」という漢字表記が少し目障りです(図004)。普通に「12:00」でよいでしょう。

図004■月の日付セルの予定時刻表示

2107003_004.png

ビューの設定項目は、「月」の日付セルに示された予定時刻と、「週」および「日」の左の時刻欄(図005)のふたつあります。

図005■週の左の時刻欄

2107003_005.png

前者がeventTimeFormat、後者はslotLabelFormatです。フォーマットの定め方は同じで、つぎのように時と分をともに2桁の数値にしました。詳しくは、「eventTimeFormat」の項をご参照ください。また、nowIndicatorは週と日のビューで、現在時刻が赤線(デフォルト)で示されます。

src/App.tsx
<FullCalendar

  eventTimeFormat={{ hour: "2-digit", minute: "2-digit" }}
  slotLabelFormat={[{ hour: "2-digit", minute: "2-digit" }]}

  nowIndicator={true}

/>

あとひとつ、月のビューで日付に「日」はなくてよさそうです(前掲図004)。実際、英語表示(デフォルト)では日付には数字しかありません(公式作例参照。ちなみに、時刻はam/pmの12時間表記です)。探してみても、ことさら数字に単位をつけるようなオプション設定が見当たらないのです。

ただ、日付セルに示されるテキストは、dayCellContentで読み書きできました。そこで、取り出した文字列から「日」を除いて戻すことにより、数字だけにしたのがつぎのコードです。与えるのはコールバックで、引数(event)からdayNumberTextで日付のテキストを参照して書き替えました。

src/App.tsx
import FullCalendar, {

  DayCellContentArg,

} from "@fullcalendar/react";

function App() {

  return (
    <div className="demo-app">

      <div className="demo-app-main">
        <FullCalendar

          dayCellContent={(event: DayCellContentArg) =>
            (event.dayNumberText = event.dayNumberText.replace("", ""))
          }
        />
      </div>
    </div>
  );
}

ここまで手を加えたのが、冒頭に掲げたサンプル001です。併せて、ふたつのモジュールのコードを、つぎのコード001にまとめておきましょう。

コード001■ルートとサイドバーのモジュール

src/App.tsx
import { useCallback, useState } from "react";
import FullCalendar, {
  DateSelectArg,
  DayCellContentArg,
  EventApi,
  EventClickArg,
  EventContentArg,
} from "@fullcalendar/react";
import dayGridPlugin from "@fullcalendar/daygrid";
import allLocales from "@fullcalendar/core/locales-all";
import interactionPlugin from "@fullcalendar/interaction";
import timeGridPlugin from "@fullcalendar/timegrid";
import listPlugin from "@fullcalendar/list";
import { INITIAL_EVENTS, createEventId } from "./event-utils";
import Sidebar from "./Sidebar";

function App() {
  const [currentEvents, setCurrentEvents] = useState<EventApi[]>([]);
  const [weekendsVisible, setWeekendsVisible] = useState(true);
  const handleEvents = useCallback(
    (events: EventApi[]) => setCurrentEvents(events),
    []
  );
  const handleDateSelect = useCallback((selectInfo: DateSelectArg) => {
    let title = prompt("イベントのタイトルを入力してください")?.trim();
    let calendarApi = selectInfo.view.calendar;
    calendarApi.unselect();
    if (title) {
      calendarApi.addEvent({
        id: createEventId(),
        title,
        start: selectInfo.startStr,
        end: selectInfo.endStr,
        allDay: selectInfo.allDay,
      });
    }
  }, []);
  const handleEventClick = useCallback((clickInfo: EventClickArg) => {
    if (
      window.confirm(`このイベント「${clickInfo.event.title}」を削除しますか`)
    ) {
      clickInfo.event.remove();
    }
  }, []);
  const renderEventContent = (eventContent: EventContentArg) => (
    <>
      <b>{eventContent.timeText}</b>
      <i>{eventContent.event.title}</i>
    </>
  );
  const handleWeekendsToggle = useCallback(
    () => setWeekendsVisible(!weekendsVisible),
    [weekendsVisible]
  );
  return (
    <div className="demo-app">
      <Sidebar
        currentEvents={currentEvents}
        toggleWeekends={handleWeekendsToggle}
        weekendsVisible={weekendsVisible}
      />
      <div className="demo-app-main">
        <FullCalendar
          plugins={[
            dayGridPlugin,
            timeGridPlugin,
            interactionPlugin,
            listPlugin,
          ]}
          headerToolbar={{
            start: "prev,next today",
            center: "title",
            end: "dayGridMonth,timeGridWeek,timeGridDay,listMonth",
          }}
          eventTimeFormat={{ hour: "2-digit", minute: "2-digit" }}
          slotLabelFormat={[{ hour: "2-digit", minute: "2-digit" }]}
          initialView="dayGridMonth"
          eventContent={renderEventContent}
          selectable={true}
          editable={true}
          selectMirror={true}
          dayMaxEvents={true}
          navLinks={true}
          businessHours={true}
          nowIndicator={true}
          weekends={weekendsVisible}
          initialEvents={INITIAL_EVENTS}
          locales={allLocales}
          locale="ja"
          eventsSet={handleEvents}
          select={handleDateSelect}
          eventClick={handleEventClick}
          dayCellContent={(event: DayCellContentArg) =>
            (event.dayNumberText = event.dayNumberText.replace("", ""))
          }
        />
      </div>
    </div>
  );
}

export default App;
src/Sidebar.tsx
import { EventApi, formatDate } from "@fullcalendar/react";

interface Props {
  currentEvents: EventApi[];
  toggleWeekends: () => void;
  weekendsVisible: boolean;
}
const renderSidebarEvent = (event: EventApi) => (
  <li key={event.id}>
    <b>
      {formatDate(event.start!, {
        year: "numeric",
        month: "short",
        day: "numeric",
        locale: "ja",
      })}
    </b>
    <i>{event.title}</i>
  </li>
);
const Sidebar: React.VFC<Props> = ({
  currentEvents,
  toggleWeekends,
  weekendsVisible,
}) => (
  <div className="demo-app-sidebar">
    <div className="demo-app-sidebar-section">
      <h2>操作方法</h2>
      <ul>
        <li>日付を選んでイベント作成</li>
        <li>ドラッグ&amp;ドロップでイベントの長さ変更</li>
        <li>クリックでイベント削除</li>
      </ul>
    </div>
    <div className="demo-app-sidebar-section">
      <label>
        <input
          type="checkbox"
          checked={weekendsVisible}
          onChange={toggleWeekends}
        />
        週末を表示
      </label>
    </div>
    <div className="demo-app-sidebar-section">
      <h2>予定一覧({currentEvents.length})</h2>
      <ul>{currentEvents.map(renderSidebarEvent)}</ul>
    </div>
  </div>
);

export default Sidebar;

シリーズ

FullCalendar React+TypeScript Example Projectを関数コンポーネントで書いてみる 01
FullCalendar React+TypeScript Example Projectを関数コンポーネントで書いてみる 02

7
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?