1ilsang

Developer who will be a Legend

[Blog] 모달 만들기 정리

2020-01-07 1ilsangDev Log

Cover

항상 모달 만들때마다 CSS 검색하느라 짜잉났는데 블로그에 달아놓은 기념으로 이번에 정리해 보려고 한다.

src -
    |- components -
                  |- Todo -
                          |- Card.js
                          |- data.js
                          |- index.js
                          |- List.js
                          |- Modal.js
                          |- ModalStyle.js
                          |- TodoList.js

발퀄 ㅈㅅ;

현재 블로그의 구조는 위와 같다. src 폴더 밑에 components 가 있고 컴포넌트 밑에 Todo 도메인이 있다. 이 안에 Todo 에 관련된 요소들이 다 들어가있다.

CSS 모듈은 Styled-Components를 사용하고 있는데, 모달 CSS가 좀 길어져서 ModalStyle 로 빼줬다.

아직 Modal 을 사용하는 곳이 Todo 밖에 없어서 여기에 박아넣긴했는데 Common 폴더로 추상화한 객체를 올리거나 Modal 도메인을 따로 만들던가 해야할것 같다.

컴포넌트들이 모두 js로 구성되어 있어서 조금 근본없다. tsx 로 언제 다 바꾸징..

Todo architecture

어쨌든 TodoList 가 전체적인 레이아웃이고 이 안에 List 들이 존재하며 List 안에 Card 들이 있다. 이 Card 들을 onClick 하면 Modal 이 open 된다.

얘네들이 중요한게 아니니까 바로 모달 컴포넌트를 소개하려고 한다.

const Card = props => {
  const { ele } = props;

  const [modalState, setModalState] = useState(false);
  const openModal = () => {
    setModalState(true);
  };

  const closeModal = event => {
    event.preventDefault();
    setModalState(false);
  };

  return (
    <>
      <TodoCard
        type={ele.type}
        draggable
        key={ele.id}
        onClick={openModal}
        onDragStart={event => onDragStart(event)}
        onDragEnd={event => onDragEnd(event)}
      >
        {ele.title}
      </TodoCard>
      <Modal data={ele} state={modalState} closeModal={closeModal} />
    </>
  );
};

모달 컴포넌트는 Card 컴포넌트와 함께 존재하는데, 각 카드마다 Modal 이 숨어져 있는 구조다.

Hooks 로 상태관리를 하니까 진짜!! 엄청나게 편하다. 좀 더 딥다이브 해봐야겠다.

Card 를 클릭하게 되면 modalState 가 true 로 변경되고 Modal 이 visible 된다.

const Modal = props => {
  const { data, state, closeModal } = props;

  return state ? (
    <Container>
      <Overlay onClick={event => closeModal(event)} />
      <Contents>
        <Title>
          {data.title}
          <Close onClick={event => closeModal(event)}>X</Close>
        </Title>
        <Body>{data.description}</Body>
      </Contents>
    </Container>
  ) : (
    <></>
  );
};

Modal.propTypes = {
  data: PropTypes.object.isRequired,
  state: PropTypes.bool.isRequired,
  closeModal: PropTypes.func.isRequired
};

props 로 state 를 가져와서 자신의 상태를 확인하고 모달을 그리거나 삭제한다.

여기서 제일 중요한게 Overlay 다.

나는 만들어진 모달들 중 X 혹은 close 를 눌러야만 꺼지는 모달이 넘모 싫다. 모달의 밖 회색 부분을 클릭했을 때 반드시 자연스럽게 닫혀야 한다고 생각한다. 바로 이 역할을 Overlay 가 해결한다.

Modal architecture

ModalStyle 코드 보기
const Container = styled.div`
  position: fixed;
  width: 100%;
  height: 100%;
  z-index: 1;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  display: flex;
  justify-content: center;
  align-items: center;
`;

const Overlay = styled.div`
  position: absolute;
  width: 100%;
  height: 100%;
  background-color: rgba(255, 255, 255, 0.8);
`;

const Contents = styled.div`
  position: relative;
  top: 0px;
  padding: 0 auto;
  border-radius: 10px;
  box-shadow: 0 10px 20px rgba(0, 0, 0, 0.19), 0 6px 6px rgba(0, 0, 0, 0.23);
  background-color: white;
  text-align: center;
  width: 50%;
  height: 600px;
`;

const Title = styled.div`
  display: flex;
  justify-content: center;
  align-items: center;
  font-size: 1.5em;
  background-color: #fcf3cf;
  width: 100%;
  overflow: auto;
  height: 10%;
`;

const Close = styled.div`
  position: absolute;
  margin-right: 15px;
  color: gray;
  right: 0;

  &:hover {
    cursor: pointer;
  }
`;

const Body = styled.div`
  margin: 10px 10px 10px 10px;
`;

export { Container, Contents, Overlay, Title, Close, Body };


모달은 전체적으로 위와 같이 구성되어 있다.

CSS 를 간단 리뷰하자면, fixed 로 부모 영역을 뚫고 전체 영역을 잡게 한 다음 absolute 로 위치를 잡는다. 이때 display: flex; 를 해줌으로써 justify-content: center;align-items: center;를 통해 가로세로 수평을 잡아줄 수 있다.

Overlay 부분을 따로 잡아서 모달 밖 영역 클릭시 closeModal 이 실행되게 했다.

만약 Overlay 를 따로 잡지않고 Container 에서 처리하려고 했다면 bubblingcapturing 때문에 고통을 맛보게 된다.

HTML Dom event flow Image ref: https://javascript.info/bubbling-and-capturing

그러므로 Overlay 라는 컴포넌트와 Contents 컴포넌트를 형제로 두어 Event flow 에 영향을 가지 않도록 해야한다.

이로써 간단 모달 끝!

Todo 모달 특성상 진행상황 표라던가 리스트업 등 Body 에 이것저것 추가해야 하는데 공수가 많이 갈 듯 하다.

그럼이만~