본문 바로가기
개발개발/WorkRoot_워크루트

데이터 피커 (react-datepicker) 달력 UI 커스텀하기

by yelimu 2025. 1. 3.

react-datepicker 라이브러리의 달력 UI 커스텀 과정 기록입니다.

react-datepicker 달력 기본 UI
완성샷


선택한 날짜 값과 관련된 로직은 제외하고 UI 컴포넌트 관련된 내용만 먼저 정리해보려고 한다. 

대략적인 구조는 이렇게 구성했다. 

 

1) BaseInput 

- 기존 DatePicker에서 제공하는 기본 input을 숨기고(inline 속성) 해당 컴포넌트를 사용했다. 

- 클릭하면 달력을 열어준다 (isOpen 상태를 토글)

- 선택할 날짜를 보여준다

2) DatePicker : react-datepicker 라이브러리에서 제공하는 input + 달력 객체

- isOpen 상태값이 true이면 달력 펼침

3) DatePickerHeader : 커스텀 달력 헤더 

4) 닫힘 버튼

- 처음에 커스텀 헤더에 추가해줬는데 이벤트 버블링때문에 제대로 작동을 안하는 문제가 발생

- 상위 컴포넌트로 빼서 absolute로 포지셔닝하니 정상 동작 하였다. 

import DatePicker from "react-datepicker";
import "react-datepicker/dist/react-datepicker.css"; // 기본 스타일 정의 
import DatePickerHeader from "./DatePickerHeader";

const DatePickerInput = ()=>{
// ... 
  return (
    <div className="relative">
    <BaseInput value={displayValue || ""}/>
    {isOpen && (
            <>
              <div
                className="absolute z-20 mt-1 h-[388px] w-[327px] rounded-lg bg-white lg:h-[584px] lg:w-[640px]"
                ref={pickerRef}
                onClick={(e) => {
                  e.stopPropagation();
                  e.preventDefault();
                }}
              >
                <DatePicker
                  inline
                  selectsRange
                  locale={ko}
                  startDate={startDate}
                  endDate={endDate}
                  onChange={handleChange}
                  minDate={startDate}
                  renderCustomHeader={(props) => <DatePickerHeader {...props} />}
                />
              </div>
              <button
                type="button"
                onMouseDown={(e) => {
                  e.stopPropagation();
                  e.preventDefault();
                  handleOpenDropdown();
                }}
                className="absolute left-[14px] top-[78px] z-30 cursor-pointer lg:top-[84px]"
              >
                <IoIosClose className="size-6 text-black-100 lg:size-9" />
              </button>
            </>
          )}
        </div>
      )
	</div>
  }

 

더보기
//DatePickerHeader.tsx
import { MdKeyboardArrowLeft, MdKeyboardArrowRight } from "react-icons/md";

interface DatePickerHeaderProps {
  date: Date;
  decreaseMonth: () => void;
  increaseMonth: () => void;
  prevMonthButtonDisabled: boolean;
  nextMonthButtonDisabled: boolean;
}

const DatePickerHeader = ({
  date,
  decreaseMonth,
  increaseMonth,
  prevMonthButtonDisabled,
  nextMonthButtonDisabled,
}: DatePickerHeaderProps) => {
  const iconStyle = "size-6 lg:size-9 text-grayscale-200";
  return (
    <div className="flex flex-col gap-2">
      <div className="lg:h-15 z-20 flex h-12 items-center justify-center text-sm font-semibold leading-6 lg:text-lg lg:font-normal lg:leading-[26px]">
        기간 선택
      </div>
      <div className="lg:h-15 mb-2 flex h-12 items-center justify-between px-[14px] py-3">
        <button type="button" onClick={decreaseMonth} disabled={prevMonthButtonDisabled}>
          <MdKeyboardArrowLeft className={iconStyle} />
        </button>
        <span className="text-base font-semibold leading-[26px] text-black-400 lg:text-[20px] lg:leading-8">
          {`${date.getFullYear()}.${String(date.getMonth() + 1).padStart(2, "0")}`}
        </span>
        <button type="button" onClick={increaseMonth} disabled={nextMonthButtonDisabled}>
          <MdKeyboardArrowRight className={iconStyle} />
        </button>
      </div>
    </div>
  );
};

export default DatePickerHeader;

 

<DatePicker
              inline
              selectsRange
              locale={ko}
              startDate={startDate}
              endDate={endDate}
              onChange={handleChange}
              minDate={startDate}
              renderCustomHeader={(props) => <DatePickerHeader {...props} />}
            />

 

먼저 데이터피커에서 지원하는 속성들을 설정해주었다.

  1. inline:
    DatePicker의 기본 input 없이 달력만 렌더 
  2. selectsRange:
    날짜 범위를 선택할 수 있도록 설정하는 속성입니다. 이 속성이 활성화되면 시작 날짜와 종료 날짜를 선택할 수 있는 두 개의 날짜 입력 필드가 나타납니다. <-> 디폴트 : 날짜 한개만 선택
  3. locale={ko}:
    ko는 한국어를 의미합니다. 이 속성은 날짜를 표시하는 방식, 예를 들어 날짜의 월, 일, 요일 등이 한국어로 표시되도록 설정합니다. locale에 설정된 값에 따라 로케일이 맞춰집니다.
  4. startDate={startDate}:
    시작 날짜를 설정하는 속성입니다. startDate는 선택 가능한 시작 날짜를 나타내며, 이 값은 상태나 props로 관리됩니다.
  5. endDate={endDate}:
    종료 날짜를 설정하는 속성입니다. endDate는 선택 가능한 종료 날짜를 나타내며, 이 값도 상태나 props로 관리됩니다.
  6. onChange={handleChange}:
    날짜가 변경될 때 호출되는 함수입니다. 날짜 범위를 선택하면 handleChange 함수가 호출되어 startDate와 endDate를 업데이트할 수 있습니다. 일반적으로 선택된 날짜를 부모 컴포넌트로 전달하거나 상태를 업데이트하는 데 사용됩니다.
  7. minDate={startDate}:
    minDate는 선택할 수 있는 최소 날짜를 설정하는 속성입니다. 이 속성에 startDate를 할당하면, 시작 날짜 이후로만 선택할 수 있도록 제한됩니다.
  8. renderCustomHeader={(props) => <DatePickerHeader {...props} />}:
    renderCustomHeader는 달력의 헤더를 커스터마이즈할 수 있게 해주는 속성입니다. 기본 헤더 대신 DatePickerHeader 컴포넌트를 렌더링하여 날짜 선택기의 상단을 원하는 방식으로 커스터마이징할 수 있습니다.

 

 

공식 문서에서 다양한 속성과 여러 용례를 알려주고 있다.

 

React Datepicker crafted by HackerOne

 

reactdatepicker.com

공식문서를 좀 보면서 했더라면 좋았겠다는 생각을 포스팅 하면서 뒤늦게 해본다 ㅎㅎ..

여기까지는 어찌 어찌 했는데 선택한 날짜나 날짜 범위의 색상, 호버할때 색상 등을 커스텀하는게 좀 힘들었던 기억이 난다. 

검색을 해봐도 개발자도구로 한땀한땀 클래스 이름을 짚어가면서 global.css에 추가해주는것 말고는 별 다른 수가 없는 것 같았다.

이런식으로..

그래서 그 결과가.. ▼ 넘넘 길어서 아래에.. 

더보기
/*------------------- date picker 커스텀 --------------------*/
/* 너비 채우기 */
.react-datepicker-wrapper {
  @apply border-[0.5px] border-grayscale-100;
}

.react-datepicker,
.react-datepicker__month-container {
  @apply mb-4 w-full lg:mb-6 !important;
}

.react-datepicker__month {
  @apply m-0 !important;
}
/* week 스타일 */
.react-datepicker__week,
.react-datepicker__day-names {
  @apply flex px-2 lg:px-4;
}
/* 모든 날짜 셀에 대한 기본 크기 설정 */
.react-datepicker__day {
  @apply my-[7px] flex h-7 w-full items-center justify-center text-[13px] font-medium leading-[22px] text-black-400 lg:my-[14px] lg:h-10 lg:text-[20px] lg:leading-8 !important;
}
/* 요일 헤더 스타일 */
.react-datepicker__day-name {
  @apply flex h-[42px] w-full items-center justify-center text-[13px] font-medium leading-[22px] text-grayscale-500 lg:h-16 lg:text-[20px] lg:leading-8 !important;
}

.react-datepicker__day-name,
.react-datepicker__day,
.react-datepicker__time-name {
  @apply mx-0 !important;
}

/* 헤더 스타일 */
.react-datepicker .react-datepicker__header {
  @apply bg-white;
}
.react-datepicker__header,
.react-datepicker__header--custom {
  @apply border-transparent !important;
}

/* 현재 달이 아닌 날짜 스타일 */
.react-datepicker__day--outside-month {
  @apply text-grayscale-300 !important;
}

/* date range */
.react-datepicker__day--in-selecting-range,
.react-datepicker__day--in-range,
.react-datepicker__month-text--in-selecting-range,
.react-datepicker__month-text--in-range,
.react-datepicker__quarter-text--in-selecting-range,
.react-datepicker__quarter-text--in-range,
.react-datepicker__year-text--in-selecting-range,
.react-datepicker__year-text--in-range,
.react-datepicker__day--in-selecting-range:not(.react-datepicker__day--outside-month) {
  @apply bg-primary-orange-50 text-black-400 opacity-80 !important;
}
/* 범위의 첫 날짜에 대한 스타일 */
.react-datepicker__day--range-start.react-datepicker__day--in-range {
  @apply bg-primary-orange-50 !important;
  background: linear-gradient(to left, #fff7eb 50%, transparent 50%) !important;
}

/* 범위의 마지막 날짜에 대한 스타일 */
.react-datepicker__day--range-end.react-datepicker__day--in-range {
  @apply bg-primary-orange-50 !important;
  background: linear-gradient(to right, #fff7eb 50%, transparent 50%) !important;
}

/* Start, End 날짜 스타일 - 선택 순서와 관계없이 항상 적용 */
.react-datepicker__day--range-start,
.react-datepicker__day--selecting-range-start,
.react-datepicker__day--range-end,
.react-datepicker__day--selecting-range-end,
.react-datepicker__day--selected {
  @apply relative text-grayscale-50 !important;
  z-index: 1;
}

/* Start, End 날짜의 원형 배경 스타일 */
.react-datepicker__day--range-start::before,
.react-datepicker__day--selecting-range-start::before,
.react-datepicker__day--range-end::before,
.react-datepicker__day--selecting-range-end::before,
.react-datepicker__day--selected::before {
  @apply absolute left-1/2 top-1/2 h-7 w-7 -translate-x-1/2 -translate-y-1/2 rounded-full bg-primary-orange-300 content-[''] lg:h-10 lg:w-10 !important;
  z-index: -1;
}

/* 날짜 호버 스타일 */
.react-datepicker__day:hover,
.react-datepicker__day--selecting-range-start:hover,
.react-datepicker__day--selecting-range-end:hover {
  @apply bg-primary-orange-50 !important;
}
/* 오늘 날짜 기본 스타일 */
.react-datepicker__day--today {
  @apply border-transparent bg-transparent font-bold !important;
  color: theme("colors.black.400") !important;
}
/* 오늘 날짜가 범위 안에 있을 때의 스타일 */
.react-datepicker__day--today.react-datepicker__day--in-range {
  @apply bg-primary-orange-50 !important;
}

/* 오늘 날짜가 시작일이나 종료일일 때의 스타일 */
.react-datepicker__day--today.react-datepicker__day--range-start::before,
.react-datepicker__day--today.react-datepicker__day--range-end::before {
  @apply bg-primary-orange-300 !important;
}

/* 종료일 스타일 */
.react-datepicker__day--selecting-range-end,
.react-datepicker__day--selected {
  @apply relative text-grayscale-50 !important;
  background: linear-gradient(to right, #fff7eb 50%, transparent 50%) !important;
}

/* 범위 선택 중 종료일 스타일 유지 */
.react-datepicker__day--in-selecting-range.react-datepicker__day--selecting-range-end,
.react-datepicker__day--in-range.react-datepicker__day--range-end {
  @apply text-grayscale-50 !important;
  background: linear-gradient(to right, #fff7eb 50%, transparent 50%) !important;
}

/* 시작일 스타일 */
.react-datepicker__day--selecting-range-start,
.react-datepicker__day--selected {
  @apply relative text-grayscale-50 !important;
  background: linear-gradient(to right, #fff7eb 50%, transparent 50%) !important;
}

/* 범위 선택 중 시작일 스타일 유지 */
.react-datepicker__day--in-selecting-range.react-datepicker__day--selecting-range-start,
.react-datepicker__day--in-range.react-datepicker__day--range-start {
  @apply text-grayscale-50 !important;
  background: linear-gradient(to left, #fff7eb 50%, transparent 50%) !important;
}

/* 첫번째 날짜 선택 후 이전 날짜에 호버했을때 첫번째 날짜 스타일 */
.react-datepicker__day--keyboard-selected,
.react-datepicker__month-text--keyboard-selected,
.react-datepicker__quarter-text--keyboard-seleted,
.react-datepicker__year-text--keyboard-selected {
  @apply bg-grayscale-50 !important;
}

.react-datepicker__day--disabled {
  @apply bg-grayscale-200 bg-opacity-50;
}

.react-datepicker__day--disabled {
  @apply rounded-sm bg-grayscale-100 bg-opacity-50;
}

/* --------------------- date picker커스텀 끝--------------------- */

 

global.css 를 작성하는데 이 분의 블로그를 참고했습니다!!

https://e-juhee.tistory.com/entry/react-datepicker

 

[react-datepicker] datepicker custom

react-datepicker로 커스텀 성공ㅎ_ㅎ README * : 필수 인자 // DatePicker : 데이트피커를 이용해 시작 날짜와 종료 날짜를 입력받습니다. 입력 인자 * startDate : 시작 날짜 (string) 초기값으로 현재 날짜를 stri

e-juhee.tistory.com

 

 

참고 ! global.css 에서  tailwind 유틸리티 속성을 적용하기 위해서는 @apply 를 앞에 붙여주어야 한다.