React
[Part.1] React 달력 제작하기
alotus
2024. 11. 20. 00:17
728x90
반응형
프로젝트를 진행하다보면 달력을 필요로 하는 일이 많다.
초기에는 달력 라이브러리 사용을 고려했으나, 다음과 같은 이유로 직접 제작하여 사용해 보기로 결정했다.
1. tailwindcss를 활용한 커스텀의 어려움
2. 다른 프로젝트에서도 사용 가능한 달력 컴포넌트 제작
[목표]
1. 단일/범위 선택 기능
2. 오늘 기준 이전 달 이동 X
3. 오늘 기준 이전 날짜 클릭 X
Part.1 달력 구현하기
이전 달 이동, 이전 날짜 클릭을 막기 위해 today 변수를 추가하고,
달력 기능 구현을 위해 useState 추가.
초기 값은 today로 설정해 주었다.
const today = new Date();
const [currentDate, setCurrentDate] = useState(today);
달력을 구성하기 위한 변수들 선언
const year = currentDate.getFullYear(); // 현재 연도
const month = currentDate.getMonth(); // 현재 월
const firstDay = new Date(year, month, 1); // 현재 월의 첫 날
const lastDay = new Date(year, month + 1, 0); // 현재 월의 마지막 날
요일과 날짜 배열을 만들기
한 주는 일요일부터 시작.
요일의 경우 Date로는 0~6으로 변환되므로, 이를 활용해 원하는 요일부터 1일이 시작되도록
현재 월의 요일 번호인 firstDay.getDay()와 마지막 날인 lastDay.getDate()를 더해준다.
그리고 .fill 메서드로 빈 문자열을 추가.
const days = ["일", "월", "화", "수", "목", "금", "토"];
const dates = Array(firstDay.getDay() + lastDay.getDate()).fill("");
처음에 만든 set 변수를 활용해 이전 달, 다음 달로 이동하기 위한 버튼을 만든다.
const prevMonth = () => {
setCurrentDate(new Date(year, month - 1, 1));
};
const nextMonth = () => {
setCurrentDate(new Date(year, month + 1, 1));
};
JSX 부분 코드는 아래와 같다.
이전 버튼의 경우 오늘 년월과 currentDate의 년월이 동일하다면 disabled가 활성화되도록 했다.
날짜는 currentDate의 현재 월의 요일 숫자와 같거나 이상일 때만 보이도록 했다.
<div
className={twMerge(
"absolute top-[120%] z-50 hidden w-full rounded-lg border border-gray-200 bg-white p-4 shadow-sm",
active && "block",
)}
>
<div className="mb-4 flex items-center gap-3">
<p className="flex-auto text-xl font-semibold text-gray-800">
{dateFormat(String(currentDate), "yy.MM")}
</p>
<button
type="button"
onClick={prevMonth}
disabled={
dateFormat(String(today), "yy.MM") ===
dateFormat(String(currentDate), "yy.MM")
}
className="h-6 w-6 disabled:cursor-not-allowed disabled:opacity-40"
>
<ChevronLeftIcon className="" />
</button>
<button
type="button"
onClick={nextMonth}
className="h-6 w-6 disabled:opacity-40"
>
<ChevronRightIcon className="" />
</button>
</div>
<div>
<div className="grid grid-cols-7">
{days.map((day) => (
<span className="cursor-default p-2 text-center first:text-rose-600">
{day}
</span>
))}
</div>
<div className="group grid grid-cols-7">
{dates.map((_, date) =>
date >= firstDay.getDay() ? (
<button
type="button"
className={twMerge(
"rounded p-2 text-gray-600 hover:bg-gray-50",
!(date % 7) && "text-rose-600 hover:bg-rose-50",
)}
>
{date >= firstDay.getDay() && date - firstDay.getDay() + 1}
</button>
) : (
<span />
),
)}
</div>
</div>
</div>
728x90
반응형