[React] 재활용 가능한 Custom Modal 만들기

박세화·2023년 7월 25일

React JS

목록 보기
13/22

🌿 custom Modal

Modal.tsx

import styled from "styled-components";
import Button from "./button.component";

interface modalProps {
  success: boolean;
  isModalOpen: boolean;
  close?(): void;
  successFunc?(): void;
  label: string;
  additional?: any;
  width: string;
  height: string;
  background: string;
  textColor: string;
  dimmed: string;
  fontSize?: string;
}

interface defaultPropsType {
  width: string;
  height: string;
  background: string;
}
  
const Modal = ({
  success,
  isModalOpen,
  additional,
  label,
  close,
  successFunc,
  width,
  height,
  background,
  textColor,
  dimmed,
  fontSize,
}: modalProps) => {
  return (
    <div>
      {isModalOpen && (
        <ModalBackdrop dimmed={dimmed}>
          <Wrapper width={width} height={height} background={background}>
            <InsideBox>
              <Body>
                <Label textColor={textColor} fontSize={fontSize}>
                  {label.split("\n").map((letter, index) => (
                    <div key={index}>
                      {letter}
                      <br />
                    </div>
                  ))}
                </Label>
                {additional ? (
                  <Contents textColor={textColor}>{additional}</Contents>
                ) : null}
              </Body>
              <ModalButton
                className="close"
                onClick={success ? successFunc : close}
              >
                확인
              </ModalButton>
            </InsideBox>
          </Wrapper>
        </ModalBackdrop>
      )}
    </div>
  );
};

export default Modal;
  • isModalOpentrue일 때에만 요소들이 보여진다.

  • ModalBackdrop은 모달이 열렸을 때 뒷 배경 딤처리를 해주기 위해 감싸진 박스이고, Wrapper가 모달 영역이다.

  • 모달 내부는 Body부분과 ModalButton 부분으로 나누어진다.

  • 먼저 모든 모달에 공통적으로 들어있는 내용물("잘못된 비밀번호입니다", "삭제하시겠습니까?" 등)은 Label 컴포넌트에 담긴다.
    (가운데 줄바꿈 관련은 이 포스트에 정리해놓았다.)

  • 그리고 만약 라벨 밑에 추가적인 내용이 들어간다면 additional prop이 true가 되어 추가 콘텐츠 Contents가 보여진다.

  • success 은 모달이 그냥 확인 버튼을 누르고 닫히기만 하면 되는 종류인지, 혹은 확인 버튼을 누르고 닫힐 때 별개의 함수가 실행돼야 하는지 구분해주는 prop이다.


🌿 모달의 열고 닫음을 관리하는 hook

useModal.tsx

import { useContext, useState } from "react";
import Modal from "../components/Modal";
import { WakeUpTimeContext } from "../contexts/wakeupTimeReducer.context";

export const useModal = () => {
  const data = useContext(WakeUpTimeContext);

  const [isModalOpen, setIsModalOpen] = useState<boolean>(false);

  const openModal = (): void => {
    setIsModalOpen(true);
  };
  const closeModal = (): void => {
    setIsModalOpen(false);
  };

  const successFunc = (): void => {
    closeModal();
    localStorage.setItem('user-wakeup-time', JSON.stringify(data))
  }

  return {
    Modal,
    isModalOpen,
    openModal,
    closeModal,
    successFunc,
  };
};
  • 위에서 언급한 success가 true이면 successFunc를 실행한다. 여기선 로컬 스토리지에 데이터를 저장하는 코드를 포함했다
    (모달 훅에서 외부 컴포넌트와 관련된 데이터를 다루는 것이 썩 달갑진 않았으나 지금으로선 이 방법이 최선이었다)

🌿 스타일 코드

styled-component

const Wrapper = styled.div<defaultPropsType>`
  width: ${(props) => props.width};
  height: ${(props) => props.height};
  background-color: ${(props) => props.background};
  border: none;
  border-radius: 20px;
  z-index: 1000;
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
`;

type dimmedProp = {
  dimmed: string;
};

export const ModalBackdrop = styled.div<dimmedProp>`
  //딤처리
  z-index: 3;
  position: fixed;
  display: flex;
  justify-content: center;
  align-items: center;
  background-color: ${(props) => props.dimmed};
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
`;

const InsideBox = styled.div`
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 32px;
  width: fit-content;
  height: fit-content;
`;

const Body = styled.div`
  height: fit-content;
  position: relative;
  top: -1rem;
`;

type TextColor = {
  textColor: string;
  fontSize?: string;
};

const Label = styled.h6<TextColor>`
  font-size: ${(props) => props.fontSize || "16px"};
  font-style: normal;
  font-weight: 500;
  line-height: normal;
  letter-spacing: -0.32px;
  text-align: center;
  margin-bottom: 0;
  white-space: pre-wrap;
  color: ${(props) => props.textColor};
`;

const Contents = styled.h6<TextColor>`
  font-size: ${(props) => props.fontSize || "16px"};
  font-style: normal;
  font-weight: 500;
  line-height: normal;
  letter-spacing: -0.32px;
  text-align: center;
  position: relative;
  top: 16px;
  margin-bottom: 0;
  white-space: pre-wrap;
  color: ${(props) => props.textColor};
  height: width;
`;

const ModalButton = styled(Button)`
  display: flex;
  width: 300px;
  height: 52px;
  padding: 12px 40px;
  justify-content: center;
  align-items: center;
  flex-shrink: 0;
  font-size: 16px;
  position: relative;
  top: 1.5rem;
`;  

Modal.defaultProps = {
  width: "330px",
  height: "180px",
  background: "#FFF",
  textColor: "#4D4D4D",
  dimmed: "rgba(0, 0, 0, 0.60);",
};

👀 코드가....똑똑하고 깔끔하다는 생각은 안든다 😅 하지만 일단 완성한 것에 의의를..

0개의 댓글