From 923bb35da1a9076ab4e2c93e16e3f00056f0966c Mon Sep 17 00:00:00 2001 From: Jeongmin Lee Date: Thu, 8 Aug 2024 01:36:53 +0900 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat:=20=EC=BA=98=EB=A6=B0=EB=8D=94?= =?UTF-8?q?=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EA=B5=AC=ED=98=84=20?= =?UTF-8?q?#15?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Calendar/Calendar.tsx | 175 ++++++++++++++++++ .../core/Button/IconButton/IconButton.tsx | 1 + 2 files changed, 176 insertions(+) create mode 100644 src/components/Calendar/Calendar.tsx diff --git a/src/components/Calendar/Calendar.tsx b/src/components/Calendar/Calendar.tsx new file mode 100644 index 0000000..066dec6 --- /dev/null +++ b/src/components/Calendar/Calendar.tsx @@ -0,0 +1,175 @@ +import dayjs, { Dayjs } from "dayjs"; +import isBetween from "dayjs/plugin/isBetween"; +import isToday from "dayjs/plugin/isToday"; +import weekday from "dayjs/plugin/weekday"; +import weekOfYear from "dayjs/plugin/weekOfYear"; +import React, { useState } from "react"; + +import { cn } from "@/utils/cn"; + +import IconButton from "../core/Button/IconButton/IconButton"; +import { ArrowLeftSmallIcon, ArrowRightSmallIcon } from "../icons"; + +dayjs.extend(weekday); +dayjs.extend(weekOfYear); +dayjs.extend(isToday); +dayjs.extend(isBetween); + +const Calendar = () => { + const [currentDate, setCurrentDate] = useState(dayjs()); + const [startDate, setStartDate] = useState(undefined); + const [endDate, setEndDate] = useState(undefined); + + const generateCalendar = (date: Dayjs) => { + const days = []; + const startOfMonth = date.startOf("month"); + const endOfMonth = date.endOf("month"); + const startOfWeek = startOfMonth.startOf("week"); + const endOfWeek = endOfMonth.endOf("week"); + + let currentDay = startOfWeek; + + while (currentDay.isBefore(endOfWeek) || currentDay.isSame(endOfWeek)) { + days.push({ + date: currentDay.format("YYYY-MM-DD"), + isCurrentMonth: currentDay.isBetween( + startOfMonth, + endOfMonth, + "day", + "[]", + ), + isToday: currentDay.isToday(), + isInRange: + startDate && + endDate && + currentDay.isBetween(startDate, endDate, "day", "[]"), + isStart: startDate && currentDay.isSame(startDate, "day"), + isEnd: endDate && currentDay.isSame(endDate, "day"), + }); + currentDay = currentDay.add(1, "day"); + } + + // ? 항상 달력에 6줄을 보장함. + const totalDays = days.length; + // const weeks = Math.ceil(totalDays / 7); + const extraDays = 6 * 7 - totalDays; + + for (let i = 0; i < extraDays; i++) { + days.push({ + date: "", + isCurrentMonth: false, + isToday: false, + isInRange: false, + isStart: false, + isEnd: false, + }); + } + + return days; + }; + + const calendarDays = generateCalendar(currentDate); + + const handlePrevMonth = () => { + setCurrentDate(currentDate.subtract(1, "month")); + }; + + const handleNextMonth = () => { + setCurrentDate(currentDate.add(1, "month")); + }; + + const handleDayClick = (date: string) => { + if (!startDate || (startDate && endDate)) { + setStartDate(dayjs(date)); + setEndDate(undefined); + } else if (startDate && !endDate) { + if (dayjs(date).isBefore(startDate)) { + setEndDate(startDate); + setStartDate(dayjs(date)); + } else { + setEndDate(dayjs(date)); + } + } + }; + + return ( +
+
+ + } + onClick={handlePrevMonth} + className="text-lg rounded-full p-2 hover:bg-gray-200" + /> + + + {currentDate.format("YYYY.MM")} + + + } + onClick={handleNextMonth} + className="text-lg rounded-full p-2 hover:bg-gray-200" + /> +
+
+ {["S", "M", "T", "W", "T", "F", "S"].map((day, index) => ( +
+ {day} +
+ ))} + {calendarDays.map((day, index) => ( +
handleDayClick(day.date)} + className={cn( + "w-12 h-12 flex justify-center items-center cursor-pointer relative", + day.isCurrentMonth + ? "text-gray-scale-700" + : "text-gray-scale-500", + day.isInRange ? "bg-primary-04" : "", + day.isStart ? "bg-primary-01 text-white rounded-full z-[99]" : "", + day.isEnd ? "bg-primary-01 text-white rounded-full z-[99]" : "", + )} + > + {day.date ? dayjs(day.date).date() : ""} + {day.isStart && ( + <> + {!!startDate && !!endDate && ( +
+ )} +
+ + )} + {day.isEnd && ( + <> + {!!startDate && !!endDate && ( +
+ )} +
+ + )} +
+ ))} +
+
+ ); +}; + +export default Calendar; diff --git a/src/components/core/Button/IconButton/IconButton.tsx b/src/components/core/Button/IconButton/IconButton.tsx index 3d017bd..4c359f3 100644 --- a/src/components/core/Button/IconButton/IconButton.tsx +++ b/src/components/core/Button/IconButton/IconButton.tsx @@ -11,6 +11,7 @@ export interface Props const IconButton: FC = ({ icon, active = false, ...props }) => { return (