웹과 모바일에 사용되는 캘린더가 커스터마이징된 캘린더를 만들어야 하는 상황이 생겨서 캘린더 라이브러리를 사용하지 않고 캘린더를 만드는 작업을 진행했습니다. 날짜 라이브러리는 day.js를 사용하여 진행했습니다.
열심히 찾아보다가 괜찮은 방법이 있어서 정리해서 공유합니다.
제가 커스텀해서 스타일을 입힌 캘린더의 디자인은 아래와 같이 생겼습니다...
본 포스팅에서는 스타일 적용에 대해서는 다루고 있지 않습니다.
모바일
웹
처음에는 어떤식으로 구현을 해야하는지 막연하게 다른분들이 구현한 소스코드를 확인했습니다.
다른 분들이 구현해놓은 소스코드는 저의 나쁜 머리로는 이해할 수 없는 소스코드였습니다.. 제가 머리가 나빠서 이해하는데 시간을 투자하고 싶지않았습니다..
아래에는 구현된 코드입니다.
import dayjs from "dayjs";
export const generateDate = (date: dayjs.Dayjs) => {
const firstDateOfMonth = dayjs(date).startOf("month");
const lastDateOfMonth = dayjs(date).endOf("month");
const arrayofDate = [];
// prefix 날짜 생성
for (let i = 0; i < firstDateOfMonth.day(); i++) {
arrayofDate.push({ currentMonth: false, date: firstDateOfMonth.day(i) });
}
// 현재 달 날짜 생성
for (let i = firstDateOfMonth.date(); i <= lastDateOfMonth.date(); i++) {
arrayofDate.push({
currentMonth: true,
date: firstDateOfMonth.date(i),
});
}
const remaining = 42 - arrayofDate.length;
// postfix 날짜 생성
for (
let i = lastDateOfMonth.date() + 1;
i <= lastDateOfMonth.date() + remaining;
i++
) {
arrayofDate.push({ currentMonth: false, date: lastDateOfMonth.date(i) });
}
return arrayofDate;
};
generateDate 함수를 통해 만들고자하는 날짜를 모두 가져옵니다.
import { useEffect, useState } from "react";
import {
CalendarContainer,
CalendarHeader,
DateContainer,
DateLabel,
DayContainer,
} from "./styles";
import dayjs from "dayjs";
import { generateDate } from "./generateDate";
interface DateProps {
currentMonth: boolean;
date: dayjs.Dayjs;
}
export const Calendar = () => {
const [date, setDate] = useState(dayjs());
const dates = [
"일요일",
"월요일",
"화요일",
"수요일",
"목요일",
"금요일",
"토요일",
];
const [records, setRecords] = useState<DateProps[]>([]);
const prevMonth = () => {
setDate((prev) => prev.add(-1, "month"));
};
const nextMonth = () => {
setDate((prev) => prev.add(1, "month"));
};
useEffect(() => {
const arrayOfDate = generateDate(date);
setRecords(arrayOfDate);
}, [date]);
return (
<CalendarContainer>
<CalendarHeader>
<div onClick={prevMonth}>
<button>{` < `}</button>
</div>
<div className="block xl:hidden">
<span>
{date.year()}년 {date.month() + 1}월
</span>
</div>
<div onClick={nextMonth}>
<button>{` > `}</button>
</div>
</CalendarHeader>
<DateContainer>
{dates.map((date, index) => (
<div key={index}>{date}</div>
))}
</DateContainer>
<DayContainer>
{records.map(({ date, currentMonth }) => {
return (
<DateLabel $currentMonth={currentMonth}>{date.date()}</DateLabel>
);
})}
</DayContainer>
</CalendarContainer>
);
};
Calendar 컴포넌트로 렌더링해서 캘린더를 만듭니다.
import styled from "styled-components";
export const CalendarContainer = styled.div`
padding: 6px;
border-radius: 8px;
`;
export const CalendarHeader = styled.div`
display: flex;
justify-content: center;
align-items: center;
`;
export const DateContainer = styled.div`
display: grid;
grid-template-columns: repeat(7, minmax(0, 1fr));
grid-gap: 8px;
`;
export const DayContainer = styled.div`
display: grid;
grid-template-columns: repeat(7, minmax(0, 1fr));
grid-gap: 8px;
`;
export const DateLabel = styled.div<{ $currentMonth?: boolean }>`
color: ${(props) => (props.$currentMonth ? `black` : "gray")};
`;
보기가 너무 어려우니 Style.tsx를 이용해 스타일을 적용해줍니다.
디테일한 스타일링은 따로 진행하지 않았습니다.
구현된 코드는 git과 데모에서 확인하실 수 있습니다.
참고 : https://www.youtube.com/watch?v=s9-K02CP8hw&ab_channel=DailyWebCoding