[React]date-fns 라이브러리
date-fns 사용해서 달력 구현하기
구현 설명
이 화면에서 달력 부분을 구현해보자
설명
1. Header: 월을 바꾸는 부분이다.
2. Days: 요일을 나타내는 부분이다.
3. Cells: 달력 셀을 나타내는 부분이다. 당연하게도 이 부분이 가장 중요하다.
구현 기능
1. Header에서 월을 바꿀 수 있다.
2. 셀을 클릭하면 그 셀이 선택된다.
3. 오른쪽 일별 박스에서 화살표를 눌렀을 때도 선택되는 셀이 변경되게 한다.
라이브러리 소개
깃허브에 나와있듯이 200개 이상의 기능이 있지만 여기서는 달력에 한정지어서 설명하려고 한다.
1. format: 날짜 객체를 여러 가지 방법으로 문자열로 서식화하는 함수
const selectedDate = new Date(); //괄호에 아무것도 없으면 현재 날짜로 객체를 생성하는 것
console.log(format(selectedDate, "yyyy-MM-dd"));
// 출력결과 2023-09-08
format 형식
1) 년도와 월
- “yyyy-MM-dd”: “2023-10-13”
- “yy-MM-dd”: “23-10-13”
- “MMMM yyyy”: “October 2023”
2) 월별 형식
- “MMM yyyy”: “Oct 2023”
- “MM/yyyy”: “10/2023”
3) 요일과 날짜
- “EEEE, dd MMMM yyyy”: “Thursday, 13 October 2023”
- “EEE, dd MMM yyyy”: “Thu, 13 Oct 2023”
4) 일자 및 시간 분리
- “dd/MM/yyyy”: “13/10/2023”
- “HH:mm”: “15:30”
5) 시간과 분
- “HH:mm”: “15:30”
- “h:mm a”: “3:30 PM”
6) AM/PM 표시
- “h:mm a”: “3:30 PM”
- “hh:mm a”: “03:30 PM”
7) 24시간 형식
- “HH:mm”: “15:30”
- “HH:mm:ss”: “15:30:00”
8) 시간대 표시
- “HH:mm ZZZZ”: “15:30 UTC”
2. addMonths, subMonths: 월을 원하는 만큼 빼고 더하는 함수
const [currentMonth, setCurrentMonth] = useState(new Date());
const prevMonth = () => {
setCurrentMonth(subMonths(currentMonth, 1));
};
const nextMonth = () => {
setCurrentMonth(addMonths(currentMonth, 1));
};
3. addDays, subDays: 일을 원하는 만큼 뺴고 더하는 함수
const [selectedDate, setSelectedDate] = useState(new Date());
const prevDay = () => {
setSelectedDate(subDays(selectedDate, 1));
};
const nextDay = () => {
setSelectedDate(addDays(selectedDate, 1));
};
4. startOfMonth, endOfMonth, startOfWeek, endOfWeek: 달력 셀에 관련된 함수
startOfMonth: 현재 달의 첫번째 날짜
endOfMonth: 현재 달의 마지막 날짜
startOfWeek: 현재 날짜 주의 첫번째 날짜
endOfWeek: 현재 날짜 주의 마지막 날짜
const monthStart = startOfMonth(currentMonth); //8월 1일
const monthEnd = endOfMonth(currentMonth); //8월 31일
const startDate = startOfWeek(monthStart); //7월 30일
const endDate = endOfWeek(monthEnd); //9월 2일
최종 구현 코드
Calendar.tsx: 메인 컴포넌트로 월과 일을 변경하기 위해 date-fns 라이브러리 사용함
import React, { useState, useEffect } from "react";
import { format, addMonths, subMonths, addDays, subDays } from "date-fns";
import {
RenderHeader,
RenderDays,
RenderCells,
} from "components/calendar/CalendarRender";
import {
RenderDetail,
RenderUnscheduled,
} from "components/calendar/DetailRender";
import {
CalendarPage,
CalendarContainer,
DayContainer,
} from "components/calendar/CalendarStyledComponents";
const Calendar = () => {
const [currentMonth, setCurrentMonth] = useState(new Date());
const [selectedDate, setSelectedDate] = useState(new Date());
const calendarData = [api 반환값];
const detailData = [api 반환값];
//달력 값 변경
const prevMonth = () => {
setCurrentMonth(subMonths(currentMonth, 1));
};
const nextMonth = () => {
setCurrentMonth(addMonths(currentMonth, 1));
};
const onDateClick = (day: any) => {
setSelectedDate(day);
};
const prevDay = () => {
setSelectedDate(subDays(selectedDate, 1));
};
const nextDay = () => {
setSelectedDate(addDays(selectedDate, 1));
};
return (
<CalendarPage>
{/* 월 변경 부분 */}
<RenderHeader
currentMonth={currentMonth}
prevMonth={prevMonth}
nextMonth={nextMonth}
/>
<div className="calendar-detail">
<CalendarContainer>
{/* 요일 부분 */}
<RenderDays />
{/* 달력 셀 부분 */}
<RenderCells
currentMonth={currentMonth}
selectedDate={selectedDate}
onDateClick={onDateClick}
calendarData={calendarData}
/>
</CalendarContainer>
<DayContainer>
{/* 오른쪽 일별 박스 부분 */}
<RenderDetail
selectedDate={selectedDate}
prevDay={prevDay}
nextDay={nextDay}
detailData={detailData}
/>
</DayContainer>
</div>
</CalendarPage>
);
};
export default Calendar;
CalendarRender.tsx
1. import 부분: 달력 셀을 위해 date-fns 라이브러리를 사용함
import React from "react";
import {
format,
startOfMonth,
endOfMonth,
startOfWeek,
endOfWeek,
addDays,
} from "date-fns";
import headerLeft from "assets/main/main_left_arrow.svg";
import headerRight from "assets/main/main_right_arrow.svg";
import dayLeft from "assets/calendar/calendar_day_left_arrow.svg";
import dayRight from "assets/calendar/calendar_day_right_arrow.svg";
import {
RenderHeaderContainer,
RenderDaysContainer,
Cell,
Event,
Row,
RenderCellsContainer,
} from "./CalendarStyledComponents";
import { ICalendarData } from "types/interfaces/CalendarProcess";
2. Header: 월 변경 부분
// RenderHeader(월)
interface RenderHeaderProps {
currentMonth: Date;
prevMonth: () => void;
nextMonth: () => void;
}
export const RenderHeader: React.FC<RenderHeaderProps> = ({
currentMonth,
prevMonth,
nextMonth,
}) => {
return (
<RenderHeaderContainer>
<img src={headerLeft} alt="headerLeft" onClick={prevMonth} />
<div className="month">{format(currentMonth, "MMMM")}</div>
<img src={headerRight} alt="headerRight" onClick={nextMonth} />
</RenderHeaderContainer>
);
};
2. Days: 요일 부분
// RenderDays(요일)
export const RenderDays = () => {
const date = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
return (
<RenderDaysContainer>
{date.map((day, i) => (
<div key={i}>{day}</div>
))}
</RenderDaysContainer>
);
};
3. Cells: 달력 셀 부분
// RenderCells(달력 셀)
interface RenderCellsProps {
currentMonth: Date;
selectedDate: Date;
onDateClick: (date: Date) => void;
calendarData: [];
}
export const RenderCells: React.FC<RenderCellsProps> = ({
currentMonth,
selectedDate,
onDateClick,
calendarData,
}) => {
const monthStart = startOfMonth(currentMonth); //8월 1일
const monthEnd = endOfMonth(monthStart); //8월 31일
const startDate = startOfWeek(monthStart); //7월 30일
const endDate = endOfWeek(monthEnd); //9월 2일
const rows = [];
let days = [];
let day = startDate;
let formattedDate = "";
while (day <= endDate) {
for (let i = 0; i < 7; i++) {
formattedDate = format(day, "d");
const cloneDay = new Date(day);
days.push(
<Cell
$day={day}
$monthStart={monthStart}
$selectedDate={selectedDate}
$currentMonth={currentMonth}
key={day.toDateString()}
onClick={() => onDateClick(cloneDay)}
>
<div className="date">{formattedDate.padStart(2, "0")}</div>
</Cell>
);
day = addDays(day, 1);
}
rows.push(<Row key={day.toDateString()}>{days}</Row>);
days = [];
}
return <RenderCellsContainer>{rows}</RenderCellsContainer>;
};
달력 모양으로 만들려면 아래와 같이 스타일을 주면 된다
export const Row = styled.div`
display: flex;
`;
export const RenderCellsContainer = styled.div`
display: flex;
flex-direction: column;
`;
Detail: 오른쪽 일별 박스 부분
// 일별로 띄우기
interface RenderDetailProps {
selectedDate: Date;
prevDay: () => void;
nextDay: () => void;
detailData: [];
}
export const RenderDetail: React.FC<RenderDetailProps> = ({
selectedDate,
prevDay,
nextDay,
detailData,
}) => {
return (
<DetailContainer>
<div className="selectDate">
<img src={dayLeft} alt="dayLeft" onClick={prevDay} />
<div className="selectedDate">
{format(selectedDate, "d, eee").toLowerCase()}
</div>
<img src={dayRight} alt="dayRight" onClick={nextDay} />
</div>
</DetailContainer>
);
};
추가 구현 코드
1. 달력 셀을 보면 선택된 날짜는 파랗게 표시가 되어있다.
2. 10월에 해당하는 날짜는 진한 회색이지만 9월이나 11월에 속하는 날짜는 옅은 회색으로 되어있다.
이 두 부분을 추가적으로 구현해보자
CalendarRender.tsx > RenderCells > Cell
<Cell
$day={day}
$selectedDate={selectedDate}
$currentMonth={currentMonth}
key={day.toDateString()}
onClick={() => onDateClick(cloneDay)}
>
<div className="date">{formattedDate.padStart(2, "0")}</div>
</Cell>
CalendarStyledComponents.ts > Cell
import styled from "styled-components";
import { format, isSameMonth, isSameDay } from "date-fns";
interface CellProps {
$day: Date;
$selectedDate: Date;
$currentMonth: Date;
}
export const Cell =
styled.div <
CellProps >
`
width: 100%;
height: 111px;
border: 0.5px solid #ccc;
font-size: 12px;
font-weight: 500;
color: #969696;
cursor: pointer;
${(props) =>
isSameDay(props.$day, props.$selectedDate) &&
`
border: 0.5px solid rgb(var(--main));
color: rgb(var(--main));
box-shadow: 0 0 6px 3px rgba(50, 83, 255, 0.225);
.date{
font-size: 14px;
font-weight: 600;
}
`}
${(props) =>
!isSameMonth(props.$day, props.$currentMonth) &&
`
color: rgb(var(--border));
`}
`;
댓글남기기