s e o p p o r t . l o g

Today I Learned

모달창 구현하기

Seo Ji Won 2024. 2. 21. 23:45

저번에 찍먹해본 모달창을 이번에는 직접 구현해보았다. 까먹을 수 있으니 기록해두기..

1. 모달을 띄울 backgroud wrap(StModalWrap)과 모달창(ModalContainer)을 만든다

모달창 backgroud  wrap의 부모 요소(MainWrap)를 position relative로,

모달창 backgroud  wrap(StModalWrap)을 fixed로 설정하고

모달창 컨테이너는 absolute로 설정한 후 가운데 정렬한다.

return (
    <>
      <ResetStyles />
      <MainWrap>
        <StModalWrap> 
        	<ModalContainer></ModalContainer>
        </StModalWrap>
        <LayoutWrap>
          {isRendered && <NavHeader />}
          <Header />
          {isRendered && <Outlet />}
        </LayoutWrap>
      </MainWrap>
    </>
  );
}

const StModalWrap = styled.div`
  width: 100%;
  min-height: 100vh;
  position: fixed;
  background-color: rgba(0, 0, 0, 0.4);
  transition: all 0.3s;
`;

const ModalContainer = styled.div`
  width: 350px;
  height: 180px;
  padding: 20px;
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  background-color: white;
  display: flex;
  flex-direction: column;
  align-items: center;
  border-radius: 10px;
  border: 1px solid black;
`;

const LayoutWrap = styled.div`
  width: 710px;
  min-height: 961px;
  display: flex;
  flex-direction: column;
  align-items: center;
  background-color: #ddf2f9;
  padding-bottom: 50px;
`;
const MainWrap = styled.div`
  display: flex;
  justify-content: center;
  background-color: #eef9fd;

  position: relative;
`;

 

 

 

2. 모달상태를 토글할 state를 만들고 모달창 안의 요소에 적용한다.

  const [isModalOpen, setIsModalOpen] = useState(false);
<ModalContainer>
    <StModalText>로그아웃하시겠습니까?</StModalText>
       <div>
          <ModifyButton onClick={handleLogoutConfirmButtonClick}>확인</ModifyButton>
          <DeleteButton onClick={() => setIsModalOpen(false)}>취소</DeleteButton>
        </div>
</ModalContainer>

 

 

3. 모달상태를 토글하는 state를 프롭스로 받아 StModalWrap 스타일을 조건부 렌더링하여 isModalOpen의 상태에 따라 모달이 열리고 닫히게 한다.

const StModalWrap = styled.div`
  z-index: ${props => (props.$isModalOpen ? '10' : '-1')};
  width: 100%;
  min-height: 100vh;
  position: fixed;
  background-color: rgba(0, 0, 0, 0.4);
  transition: all 0.3s;
  opacity: ${props => (props.$isModalOpen ? '1' : '0')};
`;

모달의 열리고 닫힘을 z-index와 opacity로 조건부 렌더링하고 transition을 걸어주면 부드럽게 열리고 닫히는 모달창을 구현할 수 있다.

 

4. 프롭스 내려주기

 

 

나의 경우 로그아웃 버튼이 NavHeader에 있었고, 로그아웃 버튼을 클릭했을 때 모달창을 띄우기 위해선 뷰포트 전체를 사용해야 했기 때문에 NavHeader 의 부모 레이아웃에서 모달창을 만들었고 NavHeader에 모달상태 토글 프롭스를 넘겨주어 로그아웃 버튼을 클릭하면 set으로 모달 상태를 토글할 수 있게 하였다.

<>
      <ResetStyles />
      <MainWrap>
        <StModalWrap $isModalOpen={isModalOpen}>
          <ModalContainer>
            <StModalText>로그아웃하시겠습니까?</StModalText>
            <div>
              <ModifyButton onClick={handleLogoutConfirmButtonClick}>확인</ModifyButton>
              <DeleteButton onClick={() => setIsModalOpen(false)}>취소</DeleteButton>
            </div>
          </ModalContainer>
        </StModalWrap>
        <LayoutWrap>
          {isRendered && <NavHeader setIsModalOpen={setIsModalOpen} />}
          <Header />
          {isRendered && <Outlet />}
        </LayoutWrap>
      </MainWrap>
    </>

 

 

전체코드

// 모달과 상관없는 코드 생략

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

const handleLogoutConfirmButtonClick = () => {
    setIsModalOpen(false);
    navigate('/login');
  };

  return (
    <>
      <ResetStyles />
      <MainWrap>
        <StModalWrap $isModalOpen={isModalOpen}>
          <ModalContainer>
            <StModalText>로그아웃하시겠습니까?</StModalText>
            <div>
              <ModifyButton onClick={handleLogoutConfirmButtonClick}>확인</ModifyButton>
              <DeleteButton onClick={() => setIsModalOpen(false)}>취소</DeleteButton>
            </div>
          </ModalContainer>
        </StModalWrap>
        <LayoutWrap>
          {isRendered && <NavHeader setIsModalOpen={setIsModalOpen} />}
          <Header />
          {isRendered && <Outlet />}
        </LayoutWrap>
      </MainWrap>
    </>
  );
}

const StModalWrap = styled.div`
  z-index: ${props => (props.$isModalOpen ? '10' : '-1')};
  width: 100%;
  min-height: 100vh;
  position: fixed;
  background-color: rgba(0, 0, 0, 0.4);
  transition: all 0.3s;
  opacity: ${props => (props.$isModalOpen ? '1' : '0')};
`;

const StModalText = styled.span`
  font-family: sans-serif;
  font-size: 18px;
  margin: auto;
  text-align: center;
`;

const ModalContainer = styled.div`
  width: 350px;
  height: 180px;
  padding: 20px;
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  background-color: white;
  display: flex;
  flex-direction: column;
  align-items: center;
  border-radius: 10px;
  border: 1px solid black;
`;

const LayoutWrap = styled.div`
  width: 710px;
  min-height: 961px;
  display: flex;
  flex-direction: column;
  align-items: center;
  background-color: #ddf2f9;
  padding-bottom: 50px;
`;
const MainWrap = styled.div`
  display: flex;
  justify-content: center;
  background-color: #eef9fd;

  position: relative;
`;

export default AuthLayout;