[ TIL 221218 ] Calendar

ponyo·2022년 12월 18일
0

Today I Learned

목록 보기
30/30
post-thumbnail

Calendar

패키지 설치

npm i @emotion/styled @emotion/react react-icons 

Import

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를 리턴하는 유틸 함수 정의

HTML DOM tree & Component Design

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" : "")};

Calendar 기능 구현

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 () 메서드는 현재 설정된 연도에 따라 지정된 날짜의 월을 설정합니다.

profile
😁

0개의 댓글