본문 바로가기

TIL

[Refactoring] 바텀시트 재사용 컴포넌트로 분리하기

캘린더 일정 등록 시 기존에 모달로 표시되던 시간 선택 모달을 바텀시트로 변경하게되었다.

 

프로젝트 내 다른 페이지들에서도 스크롤 선택 형태의 기능이 바텀시트로 디자인 되어 있어, 전반적인 사용자 경험의 통일성을 높였고,

기존 모달 방식에서는 모달창이선택한 시간들을 가렸었는데 바텀시트 방식으로 변경해 사용자가 이전 선택 내용을 확인하면서 새로운 시간을 선택할 수 있게 되었다.

 

[ 이전 디자인 ] [ 변경 디자인 ]

 

 

구현 과정

 

① 기존 코드

모달형태로 작업했던 코드인데, 시간 선택 기능을 하는 컴포넌트인 ScrollPicker 를 제외하고 별도 컴포넌트로 분리하면 좋을 것 같다고 생각했다.

 return (
    <div
      onClick={onClose}
      className="fixed inset-0 flex items-center justify-center w-full h-full bg-black/70 z-50"
      <div
        className="w-full m-6 h-[258px] bg-white rounded-2xl flex items-center justify-center p-6"
        onClick={(e) => e.stopPropagation()}
      >
        <section className="w-full">
          <p className="text-center text-xl font-medium border-b-2 pb-2">
            {convertTimeFormat(`${selectedHour}:${selectedMinute}`)}
          </p>
          <div className="flex justify-center items-center">
            {/* 시간 선택 */}
            <ScrollPicker
              options={hours}
              handleScroll={handleHourScroll}
              selectedItem={selectedHour}
            />
            <span className="text-xl font-bold mx-4">:</span>
            {/* 분 선택 */}
            <ScrollPicker
              options={minutes}
              handleScroll={handleMinuteScroll}
              selectedItem={selectedMinute}
            />
          </div>
          <div className="flex justify-center items-center gap-2 text-white mt-4">
            <button onClick={onClose} className={buttonClass}>
              취소
            </button>
            <button onClick={handleConfirm} className={buttonClass}>
              확인
            </button>
          </div>
        </section>
      </div>
    </div>
      {/* 시간 선택 */}
      <ScrollPicker
        options={hours}
        handleScroll={handleHourScroll}
        selectedItem={selectedHour}
      />
      <span className="text-xl font-bold mx-4">:</span>
      {/* 분 선택 */}
      <ScrollPicker
        options={minutes}
        handleScroll={handleMinuteScroll}
        selectedItem={selectedMinute}
      />
  );

 

 

② 공통 컴포넌트로 분리

SelectDateModal 파일을 생성해서 분리한 로직을 넣고 prop로 받을 요소들에 대한 타입을 정의해주었다.

해당 컴포넌트는 다른 페이지에서도 사용될 수 있도록 공통 폴더로 분리해서 사용하도록 해주었다.

"use client";

interface DateModalProps {
  handleClose: () => void; // 모달 닫기 함수
  handleConfirm: () => void; // 선택 확인 함수
  selectedDate: string; // 선택된 날짜 상태
  children: React.ReactNode; // 자식 컴포넌트
}

const SelectDateModal = ({
  handleClose,
  handleConfirm,
  selectedDate,
  children,
}: DateModalProps) => {
  return (
   {/* 오버레이 - 클릭 시 모달 닫힘 */}
    <div
      onClick={handleClose}
      className="fixed inset-0 flex items-end justify-center w-full h-full bg-black/70 z-50"
    >
    {/* 모달 내부 컨테이너 */}
      <div
        className="w-full bg-white rounded-t-2xl flex-col items-center justify-center animate-slide-up"
        onClick={(e) => e.stopPropagation()}
      >
     {/* 모달 콘텐츠 영역 */}
        <section className="w-full px-6">
          <p className="text-center text-xl mt-9 font-medium">{selectedDate}</p>
          <hr className="my-4" />
          <div className="flex justify-center items-center">{children}</div>
        </section>
        <div className="flex justify-center items-center gap-2 text-white mt-4 border-t py-[10px] px-6">
          <button
            onClick={handleClose}
            className="flex-1 px-4 py-2 rounded-3xl border border-secondary-900 bg-white body-16-s text-secondary-900"
          >
            취소하기
          </button>
          <button
            onClick={handleConfirm}
            className="flex-1 px-4 py-2 rounded-3xl bg-secondary-900 body-16-s text-white"
          >
            적용하기
          </button>
        </div>
      </div>
    </div>
  );
};

export default SelectDateModal;

 

Tailwind CSS 커스텀 애니메이션 설정

드롭박스를 아래에서 위로 올라오게 하는 애니메이션을 추가해줬다.

  keyframes: {
      slideUp: {
      {/* 시작상태 */}
        "0%": { transform: "translateY(100%)", opacity: "0" },
      {/* 종료상태 */}
        "100%": { transform: "translateY(0)", opacity: "1" },
      },
    },
    animation: {
    {/* 사용할 애니메이션 클래스, 시작과 끝이 부드러운 효과 */}
      "slide-up": "slideUp 0.3s ease-in-out",
    },

 

 

③ 생성한 컴포넌트 적용하기

만든 SelectDateModal 을 ScrollPicker로 감싸고 필요한 props를 내려주어 적용해주었다.

return (
    <SelectDateModal
      handleClose={onClose}
      handleConfirm={handleConfirm}
      selectedDate={convertTimeFormat(`${selectedHour}:${selectedMinute}`)}
    >
      {/* 시간 선택 */}
      <ScrollPicker
        options={hours}
        handleScroll={handleHourScroll}
        selectedItem={selectedHour}
      />
      <span className="text-xl font-bold mx-4">:</span>
      {/* 분 선택 */}
      <ScrollPicker
        options={minutes}
        handleScroll={handleMinuteScroll}
        selectedItem={selectedMinute}
      />
    </SelectDateModal>
  );

 

 

완성한 형태

 

 

🧐 느낀점

기존의 모달 컴포넌트를 별도로 분리하고 공통화하는 과정에서, 코드의 재사용성과 유지보수성을 높일 수 있는 경험을 하였다.

이 경험을 바탕으로, 앞으로 새로운 기능을 구현할때 단순히 현재의 요구사항을 충족시키는 것에 이어서 재사용성과 확장성을 고려해 구현하는 것의 중요성을 깨닫게 되었다.