npm i @emotion/styled @emotion/react react-icons
Calendar.jsx
import React from 'react';
import styled from '@emtion/styled/macro';
import { BiChevronLeft, BiChevronRight } from 'react/icons/bi';
import { isSameDay } from '../utils/date';
src/utils/date
export const isSameDay = (a, b) => {
return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
}
두 날짜를 비교해서 같다면 true를 리턴하는 유틸 함수 정의
Calendar.jsx
<Base>
<Header>
<ButtonContainer>
<ArrowButton>
<BiChevronLeft />
</ArrowButton>
<Title></Title>
<ArrowButton>
<BiChevronRight />
</ArrowButton>
</ButtonContainer>
</Header>
<Table>
<TableHeader>
<tr>
<th>
</th>
</tr>
</TableHeader>
<TableBody>
<TableData>
<DisplayDate>
</DisplayDate>
</TableData>
</TableBody>
</Table>
</Base>
const Base = styled.div`
width: 100%;
height: 100vh;
padding: 24px 12px;
display: flex;
flex-direction: column;
align-items: center;
box-sizing: border-box;
background-color: #28272a;
${Header} + ${Table} {
margin-top: 36px;
}
`;
const Header = styled.div`
width: 100%;
display: flex;
justify-content: center;
align-items: center;
`;
const ButtonContainer = styled.div`
display: flex;
align-items: center;
justify-content: center;
`;
const ArrowButton = styled.button`
border: none;
border-radius: 4px;
padding: 8px 12px;
background-color: transparent;
font-size: 18px;
cursor: pointer;
color: #f8f7fa;
`;
const Title = styled.h1`
margin: 0;
padding: 8px 24px;
font-size: 24px;
font-weight: normal;
text-align: center;
color: #f8f7fa;
`;
const Table = styled.table`
border-collapse: collapse;
width: 100%;
height: 100%;
border-spacing: 0;
`;
const TableHeader = styled.thead`
padding-block: 12px;
> tr {
> th {
padding-block: 12px;
font-weight: normal;
color: #f8f7fa;
}
}
`;
const TableData = styled.td`
text-align: center;
color: #c9c8cc;
padding: 8px;
position: relative;
`;
const DisplayDate = styled.div`
color: ${({ isToday }) => isToday && '#F8F7FA'};
background-color: ${({ isToday, isSelected }) => isSelected ? '#7047EB' : isToday ? '#313133' : ''};
display: flex;
justify-content: center;
align-items: center;
border-radius: 50%;
align-self: flex-end;
position: absolute;
top: 8px;
right: 8px;
width: 36px;
height: 36px;
cursor: pointer;
`;
💡 KeyPoint
color: ${({ isToday }) => isToday && "F8F7FA"} background-color: ${({ isToday, isSelected }) => (isSelected ? "#7047EB" : isToday ? "#313133" : "")};
const DAYS = ["SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"]; // 요일
const MONTHS = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]; // 달
const Calendar = () = {
const [selectedDate, setSelectedDate] = useState(new Date()); // 선택한 날짜
const { year, month, firstDay, lastDay } = useMemo(() => {
// 선택한 날을 기준으로 첫째 날, 마지막 날, 년, 월
const year = selectedDate.getFullYear();
const month = selectedDate.getMonth();
return {
year,
month,
firstDay: new Date(year, month, 1),
lastDay: new Date(year, month + 1, 0),
};
}, [selectedDate);
const selectDate = (date) => {
// 날짜를 선택한다.
setSelectedDate(date);
};
const pad = () => [...Array(firstDay.getDay()).keys()].map((p) => <TableData key={`pad_${p}`} />); // 해당 월의 첫째 날 전 pad
const range = () =>
[...Array(lastDay.getDate()).keys()].map((d) => {
// 1일 부터 마지막 날까지 날짜 표기
const thisDay = new Date(year, month, d + 1);
const today = new Date();
return (
<TableData key={d} onClick={() => selectDate(thisDay)}>
<DisplayDate isSelected={isSameDay(selectedDate, thisDay)} isToday={isSameDay(today, thisDay)}>
{new Date(year, month, d + 1).getDate()}
</DisplayDate>
</TableData>
);
const render = () => {
// table data 를 일주일 단위로 줄바꿈한다.
const items = [...pad(), ...range()];
const weeks = Math.ceil(items.length / 7);
return [...Array(weeks).keys()].map((week) => <tr key={`week_${week}`}>{items.slice(week * 7, week * 7 + 7)}</tr>);
};
}
return (
<Base>
<Header>
<ButtonContainer>
<ArrowButton pos="left" onClick={() => selectDate(new Date(selectedDate.setMonth(selectedDate.getMonth() - 1)))}>
<BiChevronLeft />
</ArrowButton>
<Title>{`${MONTHS[month]} ${year}`}</Title>
<ArrowButton pos="right" onClick={() => selectDate(new Date(selectedDate.setMonth(selectedDate.getMonth() + 1)))}>
<BiChevronRight />
</ArrowButton>
</ButtonContainer>
</Header>
<Table>
<TableHeader>
<tr>
{
DAYS.map((day, index) => (
<th key={day} align="center">{day}</th>
))
}
</tr>
</TableHeader>
<TableBody>
{render()}
</TableBody>
</Table>
</Base>
)
}
export default Calendar;
💡 KeyPoint
Date.prototype.getDay() // getDay() 메서드는 주어진 날짜의 현지 시간 기준 요일을 반환합니다. 0은 일요일을 나타냅니다.
Date.prototype.getDate() // getDate() 메서드는 주어진 날짜의 현지 시간 기준 일을 반환합니다.
Array.prototype.keys() // keys() 메서드는 배열의 각 인덱스를 키 값으로 가지는 새로운 Array Iterator 객체를 반환합니다.
📖 Sparse Arrays에 `keys()` 사용하기
배열에 실제로 존재하는 키 값만을 포함하는 Object.keys() 와 달리, keys() 반복기는 누락된 속성이 나타나는 빈 공간을 무시하지 않습니다.
let arr = ['a', , 'c']; let sparseKeys = Object.keys(arr); let denseKeys = [...arr.keys()]; console.log(sparseKeys); // ['0', '2'] console.log(denseKeys); // [0, 1, 2]
Date.prototype.setMonth() // setMonth () 메서드는 현재 설정된 연도에 따라 지정된 날짜의 월을 설정합니다.