Skip to content

Commit

Permalink
✨ feat: 캘린더 컴포넌트 구현 #15
Browse files Browse the repository at this point in the history
  • Loading branch information
froggy1014 committed Aug 7, 2024
1 parent c1b3085 commit 923bb35
Show file tree
Hide file tree
Showing 2 changed files with 176 additions and 0 deletions.
175 changes: 175 additions & 0 deletions src/components/Calendar/Calendar.tsx
Original file line number Diff line number Diff line change
@@ -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<Dayjs | undefined>(undefined);
const [endDate, setEndDate] = useState<Dayjs | undefined>(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 (
<div className="mx-auto mt-10 max-w-md">
<div className="mb-4 flex items-center justify-center gap-[10px]">
<IconButton
icon={
<ArrowLeftSmallIcon
width={20}
height={20}
className="text-gray-scale-800"
/>
}
onClick={handlePrevMonth}
className="text-lg rounded-full p-2 hover:bg-gray-200"
/>

<span
id="currentMonth"
className="text-caption2-medium text-gray-scale-800"
>
{currentDate.format("YYYY.MM")}
</span>
<IconButton
icon={
<ArrowRightSmallIcon
width={20}
height={20}
className="text-gray-scale-800"
/>
}
onClick={handleNextMonth}
className="text-lg rounded-full p-2 hover:bg-gray-200"
/>
</div>
<div className="grid grid-cols-7">
{["S", "M", "T", "W", "T", "F", "S"].map((day, index) => (
<div
key={index}
className="flex h-12 w-12 items-center justify-center text-caption2-medium text-gray-scale-700"
>
{day}
</div>
))}
{calendarDays.map((day, index) => (
<div
key={index}
onClick={() => 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 && (
<div className="absolute right-0 top-0 z-[-1] h-[48px] w-[24px] bg-primary-04" />
)}
<div className="absolute right-0 top-0 z-[-1] h-[48px] w-[24px] rounded-br-full rounded-tr-full bg-primary-01" />
</>
)}
{day.isEnd && (
<>
{!!startDate && !!endDate && (
<div className="absolute left-0 top-0 z-[-1] h-[48px] w-[24px] bg-primary-04" />
)}
<div className="absolute left-0 top-0 z-[-1] h-[48px] w-[24px] rounded-bl-full rounded-tl-full bg-primary-01" />
</>
)}
</div>
))}
</div>
</div>
);
};

export default Calendar;
1 change: 1 addition & 0 deletions src/components/core/Button/IconButton/IconButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export interface Props
const IconButton: FC<Props> = ({ icon, active = false, ...props }) => {
return (
<button
type="button"
className={cn(
"w-auto h-auto duration-300",
"flex items-center justify-center",
Expand Down

0 comments on commit 923bb35

Please sign in to comment.