初学者がNextjsでカレンダーアプリを作ってみた。
日常の疑問から
業務などで,利用していたGoogleカレンダー。
いつも何気なく,使っているが人との予定の共有。予定の見える化。予定の内容の詳細など,実用的な面が多い。
これはどのようにできているのだろうか?
自分で再現できるのだろうか?
と思いたち,とりあえず作ってみることにした。
最終目標
この記事では個人カレンダーの実装だけで完結しませんが,以下のことが実現できたらいいなと思って作っています。
- カレンダー作成
- 認証+共有カレンダー作成
- チャット機能実装
最終的にはLINE + カレンダーのような仲のいい人との予定を合わせやすいアプリにできたらいいなと考えています。
環境
実際に,学んでいる環境に近く,かつwebアプリということでNextjsで作ってみることにした。以下使う環境
- Nextjs
- TypeScript
- taileind
- fullcalendar
fullcalendarについて
オープンソースのjavascriptライブラリのこと。
カレンダーのUIや実装を簡単にできる優れものです。
使うメリットとして
- UI画面を簡単に作れる
- 提供されているオプションが多い。
- デザインや機能をカスタマイズの自由度が高い。
- FullCalendarホームページに様々なデモやドキュメントを提供している。
環境構築
$ npx create-next-app@latest
✔ What is your project named? … my-calendar
✔ Would you like to use TypeScript? … No / Yes
✔ Would you like to use ESLint? … No / Yes
✔ Would you like to use Tailwind CSS? … No / Yes
✔ Would you like to use `src/` directory? … No / Yes
✔ Would you like to use App Router? (recommended) … No / Yes
✔ Would you like to customize the default import alias (@/*)? … No / Yes
my-calendarという名前のNextjsの環境ができました。
その環境に移動していきます。
cd my-calendar
次にfullcalendarなどをダウンロードしていきます。
$ npm install @fullcalendar/core @fullcalendar/react @fullcalendar/daygrid @fullcalendar/interaction @fullcalendar/timegrid @headlessui/react @heroicons/react
最後にteilwindを少しいじります。
詳しくは下記を参照いただければ幸いですが,tailwindは編集することでよりカスタマイズ性が増します。好みの問題なので,どちらでも構いません。(私は勉学のためカスタマイズしたのでところ参考にしてみてください。)
カスタマイズ無し
@tailwind base;
@tailwind components;
@tailwind utilities;
@tailwind base;
@tailwind components;
@tailwind utilities;
:root {
@apply bg-gray-50;
}
input {
@apply px-2;
}
.fc-header-toolbar {
display: inline-block;
padding: 4px 4px;
@apply space-x-8;
}
.fc-day {
@apply m-8;
}
.fc-scrollgrid-sync-table {
@apply w-full;
}
.fc-view {
@apply w-full h-full lg:h-3/4 xl:h-2/3 bg-white;
}
.fc-h-event {
@apply bg-violet-500 border border-violet-600 ;
}
.fc .fc-daygrid-day.fc-day-today {
@apply bg-violet-100 ;
}
.fc .fc-button{
@apply bg-violet-800 hover:bg-violet-900 ;
}
.fc .fc-button-primary:not(:disabled).fc-button-active {
@apply bg-violet-600;
}
.fc .fc-button-primary:disabled {
@apply bg-violet-600;
}
.fc-v-event {
@apply bg-violet-500 border border-violet-600 ;
}
実行は以下のコードで
npm run dev
実装コード
カレンダーの実装
以下の状態に書き換えましょう。
"use client"
import FullCalendar from '@fullcalendar/react'
import dayGridPlugin from '@fullcalendar/daygrid'
import interactionPlugin, { Draggable, DropArg } from '@fullcalendar/interaction'
import timeGridPlugin from '@fullcalendar/timegrid'
import { Fragment, useEffect, useState } from 'react'
import { Dialog, Transition } from '@headlessui/react'
import { CheckIcon, ExclamationTriangleIcon } from '@heroicons/react/20/solid'
import { EventSourceInput } from '@fullcalendar/core/index.js'
interface Event {
tile: string;
start: Date | string;
allday: boolean;
id: number;
}
export default function Home() {
return (
<>
<nav className="flex justify-between mb-12 border-b border-violet-100 p-4">
<h1 className="font-bold text-2xl text-gray-700">Calendar</h1>
</nav>
<main className="flex min-h-screen flex-col items-center justify-between p-24">
<div className="grid grid-cols-10">
<div className="col-span-8">
<FullCalendar
plugins={[
dayGridPlugin,
interactionPlugin,
timeGridPlugin
]}
headerToolbar={{
left: 'prev, next today',
center: 'title',
right: 'resourceTimelineWook, dayGridMonth, timeGridWeek'
}}
/>
</div>
</div>
</main>
</>
);
}
次のようなカレンダーができるはずです,
予定リストの作成
次に予定リスト(仮)を作ります。
丸コピ書き換え用
"use client"
import FullCalendar from '@fullcalendar/react'
import dayGridPlugin from '@fullcalendar/daygrid'
import interactionPlugin, { Draggable, DropArg } from '@fullcalendar/interaction'
import timeGridPlugin from '@fullcalendar/timegrid'
import { Fragment, useEffect, useState } from 'react'
import { Dialog, Transition } from '@headlessui/react'
import { CheckIcon, ExclamationTriangleIcon } from '@heroicons/react/20/solid'
import { EventSourceInput } from '@fullcalendar/core/index.js'
interface Event {
title: string;
start: Date | string;
allDay: boolean;
id: number;
}
export default function Home() {
const [events, setEvents] = useState([
{ title: 'event 1', id: '1' },
{ title: 'event 2', id: '2' },
{ title: 'event 3', id: '3' },
{ title: 'event 4', id: '4' },
{ title: 'event 5', id: '5' },
])
const [allEvents, setAllEvents] = useState<Event[]>([])
const [showModal, setShowModal] = useState(false)
const [showDeleteModal, setShowDeleteModal] = useState(false)
const [idToDelete, setIdToDelete] = useState<number | null>(null)
const [newEvent, setNewEvent] = useState<Event>({
title: '',
start: '',
allDay: false,
id: 0
})
return (
<>
<nav className="flex justify-between mb-12 border-b border-violet-100 p-4">
<h1 className="font-bold text-2xl text-gray-700">Calendar</h1>
</nav>
<main className="flex min-h-screen flex-col items-center justify-between p-24">
<div className="grid grid-cols-10">
<div className="col-span-8">
<FullCalendar
plugins={[
dayGridPlugin,
interactionPlugin,
timeGridPlugin
]}
headerToolbar={{
left: 'prev,next today',
center: 'title',
right: 'resourceTimelineWook, dayGridMonth,timeGridWeek'
}}
events={allEvents as EventSourceInput}
nowIndicator={true}
editable={true}
droppable={true}
selectable={true}
selectMirror={true}
/>
</div>
<div id="draggable-el" className="ml-8 w-full border-2 p-2 rounded-md mt-16 lg:h-1/2 bg-violet-50">
<h1 className="font-bold text-lg text-center">Drag Event</h1>
{events.map(event => (
<div
className="fc-event border-2 p-1 m-2 w-full rounded-md ml-auto text-center bg-white hover:bg-blue-400"
title={event.title}
key={event.id}
>
{event.title}
</div>
))}
</div>
</div>
</main>
</>
);
}
"use client"
import FullCalendar from '@fullcalendar/react'
import dayGridPlugin from '@fullcalendar/daygrid'
import interactionPlugin, { Draggable, DropArg } from '@fullcalendar/interaction'
import timeGridPlugin from '@fullcalendar/timegrid'
import { Fragment, useEffect, useState } from 'react'
import { Dialog, Transition } from '@headlessui/react'
import { CheckIcon, ExclamationTriangleIcon } from '@heroicons/react/20/solid'
import { EventSourceInput } from '@fullcalendar/core/index.js'
interface Event {
title: string;
start: Date | string;+
allDay: boolean;
id: number;
}
export default function Home() {
+ const [events, setEvents] = useState([
+ { title: 'event 1', id: '1' },
+ { title: 'event 2', id: '2' },
+ { title: 'event 3', id: '3' },
+ { title: 'event 4', id: '4' },
+ { title: 'event 5', id: '5' },
+ ])
+ const [allEvents, setAllEvents] = useState<Event[]>([])
+ const [showModal, setShowModal] = useState(false)
+ const [showDeleteModal, setShowDeleteModal] = useState(false)
+ const [idToDelete, setIdToDelete] = useState<number | null>(null)
+ const [newEvent, setNewEvent] = useState<Event>({
+ title: '',
+ start: '',
+ allDay: false,
+ id: 0
+ })
return (
<>
<nav className="flex justify-between mb-12 border-b border-violet-100 p-4">
<h1 className="font-bold text-2xl text-gray-700">Calendar</h1>
</nav>
<main className="flex min-h-screen flex-col items-center justify-between p-24">
<div className="grid grid-cols-10">
<div className="col-span-8">
<FullCalendar
plugins={[
dayGridPlugin,
interactionPlugin,
timeGridPlugin
]}
headerToolbar={{
left: 'prev,next today',
center: 'title',
right: 'resourceTimelineWook, dayGridMonth,timeGridWeek'
}}
+ events={allEvents as EventSourceInput}
+ nowIndicator={true}
+ editable={true}
+ droppable={true}
+ selectable={true}
+ selectMirror={true}
/>
</div>
+ <div id="draggable-el" className="ml-8 w-full border-2 p-2 rounded-md mt-16 lg:h-1/2 bg-violet-50">
+ <h1 className="font-bold text-lg text-center">Drag Event</h1>
+ {events.map(event => (
+ <div
+ className="fc-event border-2 p-1 m-2 w-full rounded-md ml-auto text-center bg-white hover:bg-blue-400"
+ title={event.title}
+ key={event.id}
+ >
+ {event.title}
+ </div>
+ ))}
+ </div>
</div>
</main>
</>
);
}
次のように予定リストが出てきます。
予定のドラッグ
丸コピ書き換え用
"use client"
import FullCalendar from '@fullcalendar/react'
import dayGridPlugin from '@fullcalendar/daygrid'
import interactionPlugin, { Draggable, DropArg } from '@fullcalendar/interaction'
import timeGridPlugin from '@fullcalendar/timegrid'
import { Fragment, useEffect, useState } from 'react'
import { Dialog, Transition } from '@headlessui/react'
import { CheckIcon, ExclamationTriangleIcon } from '@heroicons/react/20/solid'
import { EventSourceInput } from '@fullcalendar/core/index.js'
interface Event {
title: string;
start: Date | string;
allDay: boolean;
id: number;
}
export default function Home() {
const [events, setEvents] = useState([
{ title: 'event 1', id: '1' },
{ title: 'event 2', id: '2' },
{ title: 'event 3', id: '3' },
{ title: 'event 4', id: '4' },
{ title: 'event 5', id: '5' },
])
const [allEvents, setAllEvents] = useState<Event[]>([])
const [showModal, setShowModal] = useState(false)
const [showDeleteModal, setShowDeleteModal] = useState(false)
const [idToDelete, setIdToDelete] = useState<number | null>(null)
const [newEvent, setNewEvent] = useState<Event>({
title: '',
start: '',
allDay: false,
id: 0
})
useEffect(() => {
let draggableEl = document.getElementById('draggable-el')
if (draggableEl) {
new Draggable(draggableEl, {
itemSelector: ".fc-event",
eventData: function (eventEl) {
let title = eventEl.getAttribute("title")
let id = eventEl.getAttribute("data")
let start = eventEl.getAttribute("start")
return { title, id, start }
}
})
}
}, [])
function handleDateClick(arg: { date: Date, allDay: boolean }) {
setNewEvent({ ...newEvent, start: arg.date, allDay: arg.allDay, id: new Date().getTime() })
setShowModal(true)
}
function addEvent(data: DropArg) {
const event = { ...newEvent, start: data.date.toISOString(), title: data.draggedEl.innerText, allDay: data.allDay, id: new Date().getTime() }
setAllEvents([...allEvents, event])
}
return (
<>
<nav className="flex justify-between mb-12 border-b border-violet-100 p-4">
<h1 className="font-bold text-2xl text-gray-700">Calendar</h1>
</nav>
<main className="flex min-h-screen flex-col items-center justify-between p-24">
<div className="grid grid-cols-10">
<div className="col-span-8">
<FullCalendar
plugins={[
dayGridPlugin,
interactionPlugin,
timeGridPlugin
]}
headerToolbar={{
left: 'prev,next today',
center: 'title',
right: 'resourceTimelineWook, dayGridMonth,timeGridWeek'
}}
events={allEvents as EventSourceInput}
nowIndicator={true}
editable={true}
droppable={true}
selectable={true}
selectMirror={true}
dateClick={handleDateClick}
drop={(data) => addEvent(data)}
/>
</div>
<div id="draggable-el" className="ml-8 w-full border-2 p-2 rounded-md mt-16 lg:h-1/2 bg-violet-50">
<h1 className="font-bold text-lg text-center">Drag Event</h1>
{events.map(event => (
<div
className="fc-event border-2 p-1 m-2 w-full rounded-md ml-auto text-center bg-white hover:bg-blue-400"
title={event.title}
key={event.id}
>
{event.title}
</div>
))}
</div>
</div>
</main>
</>
);
}
"use client"
import FullCalendar from '@fullcalendar/react'
import dayGridPlugin from '@fullcalendar/daygrid'
import interactionPlugin, { Draggable, DropArg } from '@fullcalendar/interaction'
import timeGridPlugin from '@fullcalendar/timegrid'
import { Fragment, useEffect, useState } from 'react'
import { Dialog, Transition } from '@headlessui/react'
import { CheckIcon, ExclamationTriangleIcon } from '@heroicons/react/20/solid'
import { EventSourceInput } from '@fullcalendar/core/index.js'
interface Event {
title: string;
start: Date | string;
allDay: boolean;
id: number;
}
export default function Home() {
const [events, setEvents] = useState([
{ title: 'event 1', id: '1' },
{ title: 'event 2', id: '2' },
{ title: 'event 3', id: '3' },
{ title: 'event 4', id: '4' },
{ title: 'event 5', id: '5' },
])
const [allEvents, setAllEvents] = useState<Event[]>([])
const [showModal, setShowModal] = useState(false)
const [showDeleteModal, setShowDeleteModal] = useState(false)
const [idToDelete, setIdToDelete] = useState<number | null>(null)
const [newEvent, setNewEvent] = useState<Event>({
title: '',
start: '',
allDay: false,
id: 0
})
+ useEffect(() => {
+ let draggableEl = document.getElementById('draggable-el')
+ if (draggableEl) {
+ new Draggable(draggableEl, {
+ itemSelector: ".fc-event",
+ eventData: function (eventEl) {
+ let title = eventEl.getAttribute("title")
+ let id = eventEl.getAttribute("data")
+ let start = eventEl.getAttribute("start")
+ return { title, id, start }
+ }
+ })
+ }
+ }, [])
+
+ function handleDateClick(arg: { date: Date, allDay: boolean }) {
+ setNewEvent({ ...newEvent, start: arg.date, allDay: arg.allDay, id: new Date().getTime() })
+ setShowModal(true)
+ }
+
+ function addEvent(data: DropArg) {
+ const event = { ...newEvent, start: data.date.toISOString(), title: data.draggedEl.innerText, allDay: data.allDay, id: new Date().getTime() }
+ setAllEvents([...allEvents, event])
+ }
return (
<>
<nav className="flex justify-between mb-12 border-b border-violet-100 p-4">
<h1 className="font-bold text-2xl text-gray-700">Calendar</h1>
</nav>
<main className="flex min-h-screen flex-col items-center justify-between p-24">
<div className="grid grid-cols-10">
<div className="col-span-8">
<FullCalendar
plugins={[
dayGridPlugin,
interactionPlugin,
timeGridPlugin
]}
headerToolbar={{
left: 'prev,next today',
center: 'title',
right: 'resourceTimelineWook, dayGridMonth,timeGridWeek'
}}
events={allEvents as EventSourceInput}
nowIndicator={true}
editable={true}
droppable={true}
selectable={true}
selectMirror={true}
+ dateClick={handleDateClick}
+ drop={(data) => addEvent(data)}
/>
</div>
<div id="draggable-el" className="ml-8 w-full border-2 p-2 rounded-md mt-16 lg:h-1/2 bg-violet-50">
<h1 className="font-bold text-lg text-center">Drag Event</h1>
{events.map(event => (
<div
className="fc-event border-2 p-1 m-2 w-full rounded-md ml-auto text-center bg-white hover:bg-blue-400"
title={event.title}
key={event.id}
>
{event.title}
</div>
))}
</div>
</div>
</main>
</>
);
}
これで,予定がドロップアンドドロップできるようになりました。
delete modalの追加
予定を消したいので,delete modalを作ります。
丸コピ用
"use client"
import FullCalendar from '@fullcalendar/react'
import dayGridPlugin from '@fullcalendar/daygrid'
import interactionPlugin, { Draggable, DropArg } from '@fullcalendar/interaction'
import timeGridPlugin from '@fullcalendar/timegrid'
import { Fragment, useEffect, useState } from 'react'
import { Dialog, Transition } from '@headlessui/react'
import { CheckIcon, ExclamationTriangleIcon } from '@heroicons/react/20/solid'
import { EventSourceInput } from '@fullcalendar/core/index.js'
interface Event {
title: string;
start: Date | string;
allDay: boolean;
id: number;
}
export default function Home() {
const [events, setEvents] = useState([
{ title: 'event 1', id: '1' },
{ title: 'event 2', id: '2' },
{ title: 'event 3', id: '3' },
{ title: 'event 4', id: '4' },
{ title: 'event 5', id: '5' },
])
const [allEvents, setAllEvents] = useState<Event[]>([])
const [showModal, setShowModal] = useState(false)
const [showDeleteModal, setShowDeleteModal] = useState(false)
const [idToDelete, setIdToDelete] = useState<number | null>(null)
const [newEvent, setNewEvent] = useState<Event>({
title: '',
start: '',
allDay: false,
id: 0
})
useEffect(() => {
let draggableEl = document.getElementById('draggable-el')
if (draggableEl) {
new Draggable(draggableEl, {
itemSelector: ".fc-event",
eventData: function (eventEl) {
let title = eventEl.getAttribute("title")
let id = eventEl.getAttribute("data")
let start = eventEl.getAttribute("start")
return { title, id, start }
}
})
}
}, [])
function handleDateClick(arg: { date: Date, allDay: boolean }) {
setNewEvent({ ...newEvent, start: arg.date, allDay: arg.allDay, id: new Date().getTime() })
setShowModal(true)
}
function addEvent(data: DropArg) {
const event = { ...newEvent, start: data.date.toISOString(), title: data.draggedEl.innerText, allDay: data.allDay, id: new Date().getTime() }
setAllEvents([...allEvents, event])
}
function handleDeleteModal(data: { event: { id: string } }) {
setShowDeleteModal(true)
setIdToDelete(Number(data.event.id))
}
function handleDelete() {
setAllEvents(allEvents.filter(event => Number(event.id) !== Number(idToDelete)))
setShowDeleteModal(false)
setIdToDelete(null)
}
function handleCloseModal() {
setShowModal(false)
setNewEvent({
title: '',
start: '',
allDay: false,
id: 0
})
setShowDeleteModal(false)
setIdToDelete(null)
}
return (
<>
<nav className="flex justify-between mb-12 border-b border-violet-100 p-4">
<h1 className="font-bold text-2xl text-gray-700">Calendar</h1>
</nav>
<main className="flex min-h-screen flex-col items-center justify-between p-24">
<div className="grid grid-cols-10">
<div className="col-span-8">
<FullCalendar
plugins={[
dayGridPlugin,
interactionPlugin,
timeGridPlugin
]}
headerToolbar={{
left: 'prev,next today',
center: 'title',
right: 'resourceTimelineWook, dayGridMonth,timeGridWeek'
}}
events={allEvents as EventSourceInput}
nowIndicator={true}
editable={true}
droppable={true}
selectable={true}
selectMirror={true}
dateClick={handleDateClick}
drop={(data) => addEvent(data)}
eventClick={(data) => handleDeleteModal(data)}
/>
</div>
<div id="draggable-el" className="ml-8 w-full border-2 p-2 rounded-md mt-16 lg:h-1/2 bg-violet-50">
<h1 className="font-bold text-lg text-center">Drag Event</h1>
{events.map(event => (
<div
className="fc-event border-2 p-1 m-2 w-full rounded-md ml-auto text-center bg-white hover:bg-blue-400"
title={event.title}
key={event.id}
>
{event.title}
</div>
))}
</div>
</div>
<Transition.Root show={showDeleteModal} as={Fragment}>
<Dialog as="div" className="relative z-10" onClose={setShowDeleteModal}>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" />
</Transition.Child>
<div className="fixed inset-0 z-10 overflow-y-auto">
<div className="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative transform overflow-hidden rounded-lg
bg-white text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg"
>
<div className="bg-white px-4 pb-4 pt-5 sm:p-6 sm:pb-4">
<div className="sm:flex sm:items-start">
<div className="mx-auto flex h-12 w-12 flex-shrink-0 items-center
justify-center rounded-full bg-red-100 sm:mx-0 sm:h-10 sm:w-10">
<ExclamationTriangleIcon className="h-6 w-6 text-red-600" aria-hidden="true" />
</div>
<div className="mt-3 text-center sm:ml-4 sm:mt-0 sm:text-left">
<Dialog.Title as="h3" className="text-base font-semibold leading-6 text-gray-900">
Delete Event
</Dialog.Title>
<div className="mt-2">
<p className="text-sm text-gray-500">
Are you sure you want to delete this event?
</p>
</div>
</div>
</div>
</div>
<div className="bg-gray-50 px-4 py-3 sm:flex sm:flex-row-reverse sm:px-6">
<button type="button" className="inline-flex w-full justify-center rounded-md bg-red-600 px-3 py-2 text-sm
font-semibold text-white shadow-sm hover:bg-red-500 sm:ml-3 sm:w-auto" onClick={handleDelete}>
Delete
</button>
<button type="button" className="mt-3 inline-flex w-full justify-center rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900
shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 sm:mt-0 sm:w-auto"
onClick={handleCloseModal}
>
Cancel
</button>
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition.Root>
</main>
</>
);
}
"use client"
import FullCalendar from '@fullcalendar/react'
import dayGridPlugin from '@fullcalendar/daygrid'
import interactionPlugin, { Draggable, DropArg } from '@fullcalendar/interaction'
import timeGridPlugin from '@fullcalendar/timegrid'
import { Fragment, useEffect, useState } from 'react'
import { Dialog, Transition } from '@headlessui/react'
import { CheckIcon, ExclamationTriangleIcon } from '@heroicons/react/20/solid'
import { EventSourceInput } from '@fullcalendar/core/index.js'
interface Event {
title: string;
start: Date | string;
allDay: boolean;
id: number;
}
export default function Home() {
const [events, setEvents] = useState([
{ title: 'event 1', id: '1' },
{ title: 'event 2', id: '2' },
{ title: 'event 3', id: '3' },
{ title: 'event 4', id: '4' },
{ title: 'event 5', id: '5' },
])
const [allEvents, setAllEvents] = useState<Event[]>([])
const [showModal, setShowModal] = useState(false)
const [showDeleteModal, setShowDeleteModal] = useState(false)
const [idToDelete, setIdToDelete] = useState<number | null>(null)
const [newEvent, setNewEvent] = useState<Event>({
title: '',
start: '',
allDay: false,
id: 0
})
useEffect(() => {
let draggableEl = document.getElementById('draggable-el')
if (draggableEl) {
new Draggable(draggableEl, {
itemSelector: ".fc-event",
eventData: function (eventEl) {
let title = eventEl.getAttribute("title")
let id = eventEl.getAttribute("data")
let start = eventEl.getAttribute("start")
return { title, id, start }
}
})
}
}, [])
function handleDateClick(arg: { date: Date, allDay: boolean }) {
setNewEvent({ ...newEvent, start: arg.date, allDay: arg.allDay, id: new Date().getTime() })
setShowModal(true)
}
function addEvent(data: DropArg) {
const event = { ...newEvent, start: data.date.toISOString(), title: data.draggedEl.innerText, allDay: data.allDay, id: new Date().getTime() }
setAllEvents([...allEvents, event])
}
+ function handleDeleteModal(data: { event: { id: string } }) {
+ setShowDeleteModal(true)
+ setIdToDelete(Number(data.event.id))
+ }
+ function handleDelete() {
+ setAllEvents(allEvents.filter(event => Number(event.id) !== Number(idToDelete)))
+ setShowDeleteModal(false)
+ setIdToDelete(null)
+ }
+ function handleCloseModal() {
+ setShowModal(false)
+ setNewEvent({
+ title: '',
+ start: '',
+ allDay: false,
+ id: 0
+ })
+ setShowDeleteModal(false)
+ setIdToDelete(null)
+ }
return (
<>
<nav className="flex justify-between mb-12 border-b border-violet-100 p-4">
<h1 className="font-bold text-2xl text-gray-700">Calendar</h1>
</nav>
<main className="flex min-h-screen flex-col items-center justify-between p-24">
<div className="grid grid-cols-10">
<div className="col-span-8">
<FullCalendar
plugins={[
dayGridPlugin,
interactionPlugin,
timeGridPlugin
]}
headerToolbar={{
left: 'prev,next today',
center: 'title',
right: 'resourceTimelineWook, dayGridMonth,timeGridWeek'
}}
events={allEvents as EventSourceInput}
nowIndicator={true}
editable={true}
droppable={true}
selectable={true}
selectMirror={true}
dateClick={handleDateClick}
drop={(data) => addEvent(data)}
eventClick={(data) => handleDeleteModal(data)}
/>
</div>
<div id="draggable-el" className="ml-8 w-full border-2 p-2 rounded-md mt-16 lg:h-1/2 bg-violet-50">
<h1 className="font-bold text-lg text-center">Drag Event</h1>
{events.map(event => (
<div
className="fc-event border-2 p-1 m-2 w-full rounded-md ml-auto text-center bg-white hover:bg-blue-400"
title={event.title}
key={event.id}
>
{event.title}
</div>
))}
</div>
</div>
+ //以下追加
<Transition.Root show={showDeleteModal} as={Fragment}>
<Dialog as="div" className="relative z-10" onClose={setShowDeleteModal}>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" />
</Transition.Child>
<div className="fixed inset-0 z-10 overflow-y-auto">
<div className="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative transform overflow-hidden rounded-lg
bg-white text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg"
>
<div className="bg-white px-4 pb-4 pt-5 sm:p-6 sm:pb-4">
<div className="sm:flex sm:items-start">
<div className="mx-auto flex h-12 w-12 flex-shrink-0 items-center
justify-center rounded-full bg-red-100 sm:mx-0 sm:h-10 sm:w-10">
<ExclamationTriangleIcon className="h-6 w-6 text-red-600" aria-hidden="true" />
</div>
<div className="mt-3 text-center sm:ml-4 sm:mt-0 sm:text-left">
<Dialog.Title as="h3" className="text-base font-semibold leading-6 text-gray-900">
Delete Event
</Dialog.Title>
<div className="mt-2">
<p className="text-sm text-gray-500">
Are you sure you want to delete this event?
</p>
</div>
</div>
</div>
</div>
<div className="bg-gray-50 px-4 py-3 sm:flex sm:flex-row-reverse sm:px-6">
<button type="button" className="inline-flex w-full justify-center rounded-md bg-red-600 px-3 py-2 text-sm
font-semibold text-white shadow-sm hover:bg-red-500 sm:ml-3 sm:w-auto" onClick={handleDelete}>
Delete
</button>
<button type="button" className="mt-3 inline-flex w-full justify-center rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900
shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 sm:mt-0 sm:w-auto"
onClick={handleCloseModal}
>
Cancel
</button>
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition.Root>
+ // ここまで
</main>
</>
);
}
予定を作る。
googleカレンダーのように予定をタイムスケジュールで作ります。
丸コピ用
"use client"
import FullCalendar from '@fullcalendar/react'
import dayGridPlugin from '@fullcalendar/daygrid'
import interactionPlugin, { Draggable, DropArg } from '@fullcalendar/interaction'
import timeGridPlugin from '@fullcalendar/timegrid'
import { Fragment, useEffect, useState } from 'react'
import { Dialog, Transition } from '@headlessui/react'
import { CheckIcon, ExclamationTriangleIcon } from '@heroicons/react/20/solid'
import { EventSourceInput } from '@fullcalendar/core/index.js'
interface Event {
title: string;
start: Date | string;
allDay: boolean;
id: number;
}
export default function Home() {
const [events, setEvents] = useState([
{ title: 'event 1', id: '1' },
{ title: 'event 2', id: '2' },
{ title: 'event 3', id: '3' },
{ title: 'event 4', id: '4' },
{ title: 'event 5', id: '5' },
])
const [allEvents, setAllEvents] = useState<Event[]>([])
const [showModal, setShowModal] = useState(false)
const [showDeleteModal, setShowDeleteModal] = useState(false)
const [idToDelete, setIdToDelete] = useState<number | null>(null)
const [newEvent, setNewEvent] = useState<Event>({
title: '',
start: '',
allDay: false,
id: 0
})
useEffect(() => {
let draggableEl = document.getElementById('draggable-el')
if (draggableEl) {
new Draggable(draggableEl, {
itemSelector: ".fc-event",
eventData: function (eventEl) {
let title = eventEl.getAttribute("title")
let id = eventEl.getAttribute("data")
let start = eventEl.getAttribute("start")
return { title, id, start }
}
})
}
}, [])
function handleDateClick(arg: { date: Date, allDay: boolean }) {
setNewEvent({ ...newEvent, start: arg.date, allDay: arg.allDay, id: new Date().getTime() })
setShowModal(true)
}
function addEvent(data: DropArg) {
const event = { ...newEvent, start: data.date.toISOString(), title: data.draggedEl.innerText, allDay: data.allDay, id: new Date().getTime() }
setAllEvents([...allEvents, event])
}
function handleDeleteModal(data: { event: { id: string } }) {
setShowDeleteModal(true)
setIdToDelete(Number(data.event.id))
}
function handleDelete() {
setAllEvents(allEvents.filter(event => Number(event.id) !== Number(idToDelete)))
setShowDeleteModal(false)
setIdToDelete(null)
}
function handleCloseModal() {
setShowModal(false)
setNewEvent({
title: '',
start: '',
allDay: false,
id: 0
})
setShowDeleteModal(false)
setIdToDelete(null)
}
const handleChange = (e: React.ChangeEvent<HTMLInputElement>): void => {
setNewEvent({
...newEvent,
title: e.target.value
})
}
function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault()
setAllEvents([...allEvents, newEvent])
setShowModal(false)
setNewEvent({
title: '',
start: '',
allDay: false,
id: 0
})
}
return (
<>
<nav className="flex justify-between mb-12 border-b border-violet-100 p-4">
<h1 className="font-bold text-2xl text-gray-700">Calendar</h1>
</nav>
<main className="flex min-h-screen flex-col items-center justify-between p-24">
<div className="grid grid-cols-10">
<div className="col-span-8">
<FullCalendar
plugins={[
dayGridPlugin,
interactionPlugin,
timeGridPlugin
]}
headerToolbar={{
left: 'prev,next today',
center: 'title',
right: 'resourceTimelineWook, dayGridMonth,timeGridWeek'
}}
events={allEvents as EventSourceInput}
nowIndicator={true}
editable={true}
droppable={true}
selectable={true}
selectMirror={true}
dateClick={handleDateClick}
drop={(data) => addEvent(data)}
eventClick={(data) => handleDeleteModal(data)}
/>
</div>
<div id="draggable-el" className="ml-8 w-full border-2 p-2 rounded-md mt-16 lg:h-1/2 bg-violet-50">
<h1 className="font-bold text-lg text-center">Drag Event</h1>
{events.map(event => (
<div
className="fc-event border-2 p-1 m-2 w-full rounded-md ml-auto text-center bg-white"
title={event.title}
key={event.id}
>
{event.title}
</div>
))}
</div>
</div>
<Transition.Root show={showDeleteModal} as={Fragment}>
<Dialog as="div" className="relative z-10" onClose={setShowDeleteModal}>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" />
</Transition.Child>
<div className="fixed inset-0 z-10 overflow-y-auto">
<div className="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative transform overflow-hidden rounded-lg
bg-white text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg"
>
<div className="bg-white px-4 pb-4 pt-5 sm:p-6 sm:pb-4">
<div className="sm:flex sm:items-start">
<div className="mx-auto flex h-12 w-12 flex-shrink-0 items-center
justify-center rounded-full bg-red-100 sm:mx-0 sm:h-10 sm:w-10">
<ExclamationTriangleIcon className="h-6 w-6 text-red-600" aria-hidden="true" />
</div>
<div className="mt-3 text-center sm:ml-4 sm:mt-0 sm:text-left">
<Dialog.Title as="h3" className="text-base font-semibold leading-6 text-gray-900">
Delete Event
</Dialog.Title>
<div className="mt-2">
<p className="text-sm text-gray-500">
Are you sure you want to delete this event?
</p>
</div>
</div>
</div>
</div>
<div className="bg-gray-50 px-4 py-3 sm:flex sm:flex-row-reverse sm:px-6">
<button type="button" className="inline-flex w-full justify-center rounded-md bg-red-600 px-3 py-2 text-sm
font-semibold text-white shadow-sm hover:bg-red-500 sm:ml-3 sm:w-auto" onClick={handleDelete}>
Delete
</button>
<button type="button" className="mt-3 inline-flex w-full justify-center rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900
shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 sm:mt-0 sm:w-auto"
onClick={handleCloseModal}
>
Cancel
</button>
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition.Root>
<Transition.Root show={showModal} as={Fragment}>
<Dialog as="div" className="relative z-10" onClose={setShowModal}>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" />
</Transition.Child>
<div className="fixed inset-0 z-10 overflow-y-auto">
<div className="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative transform overflow-hidden rounded-lg bg-white px-4 pb-4 pt-5 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg sm:p-6">
<div>
<div className="mx-auto flex h-12 w-12 items-center justify-center rounded-full bg-green-100">
<CheckIcon className="h-6 w-6 text-green-600" aria-hidden="true" />
</div>
<div className="mt-3 text-center sm:mt-5">
<Dialog.Title as="h3" className="text-base font-semibold leading-6 text-gray-900">
Add Event
</Dialog.Title>
<form action="submit" onSubmit={handleSubmit}>
<div className="mt-2">
<input type="text" name="title" className="block w-full rounded-md border-0 py-1.5 text-gray-900
shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400
focus:ring-2
focus:ring-inset focus:ring-violet-600
sm:text-sm sm:leading-6"
value={newEvent.title} onChange={(e) => handleChange(e)} placeholder="Title" />
</div>
<div className="mt-5 sm:mt-6 sm:grid sm:grid-flow-row-dense sm:grid-cols-2 sm:gap-3">
<button
type="submit"
className="inline-flex w-full justify-center rounded-md bg-violet-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-violet-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-violet-600 sm:col-start-2 disabled:opacity-25"
disabled={newEvent.title === ''}
>
Create
</button>
<button
type="button"
className="mt-3 inline-flex w-full justify-center rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 sm:col-start-1 sm:mt-0"
onClick={handleCloseModal}
>
Cancel
</button>
</div>
</form>
</div>
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition.Root>
</main >
</>
)
}
"use client"
import FullCalendar from '@fullcalendar/react'
import dayGridPlugin from '@fullcalendar/daygrid'
import interactionPlugin, { Draggable, DropArg } from '@fullcalendar/interaction'
import timeGridPlugin from '@fullcalendar/timegrid'
import { Fragment, useEffect, useState } from 'react'
import { Dialog, Transition } from '@headlessui/react'
import { CheckIcon, ExclamationTriangleIcon } from '@heroicons/react/20/solid'
import { EventSourceInput } from '@fullcalendar/core/index.js'
interface Event {
title: string;
start: Date | string;
allDay: boolean;
id: number;
}
export default function Home() {
const [events, setEvents] = useState([
{ title: 'event 1', id: '1' },
{ title: 'event 2', id: '2' },
{ title: 'event 3', id: '3' },
{ title: 'event 4', id: '4' },
{ title: 'event 5', id: '5' },
])
const [allEvents, setAllEvents] = useState<Event[]>([])
const [showModal, setShowModal] = useState(false)
const [showDeleteModal, setShowDeleteModal] = useState(false)
const [idToDelete, setIdToDelete] = useState<number | null>(null)
const [newEvent, setNewEvent] = useState<Event>({
title: '',
start: '',
allDay: false,
id: 0
})
useEffect(() => {
let draggableEl = document.getElementById('draggable-el')
if (draggableEl) {
new Draggable(draggableEl, {
itemSelector: ".fc-event",
eventData: function (eventEl) {
let title = eventEl.getAttribute("title")
let id = eventEl.getAttribute("data")
let start = eventEl.getAttribute("start")
return { title, id, start }
}
})
}
}, [])
function handleDateClick(arg: { date: Date, allDay: boolean }) {
setNewEvent({ ...newEvent, start: arg.date, allDay: arg.allDay, id: new Date().getTime() })
setShowModal(true)
}
function addEvent(data: DropArg) {
const event = { ...newEvent, start: data.date.toISOString(), title: data.draggedEl.innerText, allDay: data.allDay, id: new Date().getTime() }
setAllEvents([...allEvents, event])
}
function handleDeleteModal(data: { event: { id: string } }) {
setShowDeleteModal(true)
setIdToDelete(Number(data.event.id))
}
function handleDelete() {
setAllEvents(allEvents.filter(event => Number(event.id) !== Number(idToDelete)))
setShowDeleteModal(false)
setIdToDelete(null)
}
function handleCloseModal() {
setShowModal(false)
setNewEvent({
title: '',
start: '',
allDay: false,
id: 0
})
setShowDeleteModal(false)
setIdToDelete(null)
}
+ const handleChange = (e: React.ChangeEvent<HTMLInputElement>): void => {
+ setNewEvent({
+ ...newEvent,
+ title: e.target.value
+ })
+ }
+ function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
+ e.preventDefault()
+ setAllEvents([...allEvents, newEvent])
+ setShowModal(false)
+ setNewEvent({
+ title: '',
+ start: '',
+ allDay: false,
+ id: 0
+ })
+ }
return (
<>
<nav className="flex justify-between mb-12 border-b border-violet-100 p-4">
<h1 className="font-bold text-2xl text-gray-700">Calendar</h1>
</nav>
<main className="flex min-h-screen flex-col items-center justify-between p-24">
<div className="grid grid-cols-10">
<div className="col-span-8">
<FullCalendar
plugins={[
dayGridPlugin,
interactionPlugin,
timeGridPlugin
]}
headerToolbar={{
left: 'prev,next today',
center: 'title',
right: 'resourceTimelineWook, dayGridMonth,timeGridWeek'
}}
events={allEvents as EventSourceInput}
nowIndicator={true}
editable={true}
droppable={true}
selectable={true}
selectMirror={true}
dateClick={handleDateClick}
drop={(data) => addEvent(data)}
eventClick={(data) => handleDeleteModal(data)}
/>
</div>
<div id="draggable-el" className="ml-8 w-full border-2 p-2 rounded-md mt-16 lg:h-1/2 bg-violet-50">
<h1 className="font-bold text-lg text-center">Drag Event</h1>
{events.map(event => (
<div
className="fc-event border-2 p-1 m-2 w-full rounded-md ml-auto text-center bg-white"
title={event.title}
key={event.id}
>
{event.title}
</div>
))}
</div>
</div>
<Transition.Root show={showDeleteModal} as={Fragment}>
<Dialog as="div" className="relative z-10" onClose={setShowDeleteModal}>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" />
</Transition.Child>
<div className="fixed inset-0 z-10 overflow-y-auto">
<div className="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative transform overflow-hidden rounded-lg
bg-white text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg"
>
<div className="bg-white px-4 pb-4 pt-5 sm:p-6 sm:pb-4">
<div className="sm:flex sm:items-start">
<div className="mx-auto flex h-12 w-12 flex-shrink-0 items-center
justify-center rounded-full bg-red-100 sm:mx-0 sm:h-10 sm:w-10">
<ExclamationTriangleIcon className="h-6 w-6 text-red-600" aria-hidden="true" />
</div>
<div className="mt-3 text-center sm:ml-4 sm:mt-0 sm:text-left">
<Dialog.Title as="h3" className="text-base font-semibold leading-6 text-gray-900">
Delete Event
</Dialog.Title>
<div className="mt-2">
<p className="text-sm text-gray-500">
Are you sure you want to delete this event?
</p>
</div>
</div>
</div>
</div>
<div className="bg-gray-50 px-4 py-3 sm:flex sm:flex-row-reverse sm:px-6">
<button type="button" className="inline-flex w-full justify-center rounded-md bg-red-600 px-3 py-2 text-sm
font-semibold text-white shadow-sm hover:bg-red-500 sm:ml-3 sm:w-auto" onClick={handleDelete}>
Delete
</button>
<button type="button" className="mt-3 inline-flex w-full justify-center rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900
shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 sm:mt-0 sm:w-auto"
onClick={handleCloseModal}
>
Cancel
</button>
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition.Root>
+ //以下追加
<Transition.Root show={showModal} as={Fragment}>
<Dialog as="div" className="relative z-10" onClose={setShowModal}>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" />
</Transition.Child>
<div className="fixed inset-0 z-10 overflow-y-auto">
<div className="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative transform overflow-hidden rounded-lg bg-white px-4 pb-4 pt-5 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg sm:p-6">
<div>
<div className="mx-auto flex h-12 w-12 items-center justify-center rounded-full bg-green-100">
<CheckIcon className="h-6 w-6 text-green-600" aria-hidden="true" />
</div>
<div className="mt-3 text-center sm:mt-5">
<Dialog.Title as="h3" className="text-base font-semibold leading-6 text-gray-900">
Add Event
</Dialog.Title>
<form action="submit" onSubmit={handleSubmit}>
<div className="mt-2">
<input type="text" name="title" className="block w-full rounded-md border-0 py-1.5 text-gray-900
shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400
focus:ring-2
focus:ring-inset focus:ring-violet-600
sm:text-sm sm:leading-6"
value={newEvent.title} onChange={(e) => handleChange(e)} placeholder="Title" />
</div>
<div className="mt-5 sm:mt-6 sm:grid sm:grid-flow-row-dense sm:grid-cols-2 sm:gap-3">
<button
type="submit"
className="inline-flex w-full justify-center rounded-md bg-violet-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-violet-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-violet-600 sm:col-start-2 disabled:opacity-25"
disabled={newEvent.title === ''}
>
Create
</button>
<button
type="button"
className="mt-3 inline-flex w-full justify-center rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 sm:col-start-1 sm:mt-0"
onClick={handleCloseModal}
>
Cancel
</button>
</div>
</form>
</div>
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition.Root>
+ //ここまで
</main >
</>
)
}
次回
次回はアカウント認証周りを追加したいと思います。
出来次第追記
Discussion