UI 컴포넌트 리액트로 구현하기 - Modal

arrrrrr·2023년 2월 21일

React 공부중 🎽

목록 보기
8/16

UI 컴포넌트의 필요성

아무리 복잡한 화면도 내부에서 사용되는 UI는 반복적으로 재사용되는 경우가 많다. 따라서 반복되는 UI는 컴포넌트로 관리하면 개발 기간을 단축할 수 있다.

디자인 시스템 이용의 이점

디자인 시스템을 이용하면 UI 컴포넌트들을 효과적으로 관리할 수 있다.
디자인 시스템은 서비스를 만드는데 사용한 컬러, 서체, 인터랙션 등을 체계적으로 정리한 것이기 때문이다.

요구사항

  • 오픈 모달 버튼 조작을 통해 모달창을 띄운다.
  • 버튼을 클릭하면 모달 배경, 모달 창을 렌더링한다.
  • 모달창이 렌더링된 상태에서 닫기 버튼을 클릭하면 모달 배경, 모달 창이 사라진다.
  • 모달 창 밖 배경을 클릭해도 모달 배경, 모달 창이 사라진다.

새로 배운 내용 - 기능

📌 UI를 만드는 프로세스

화면부터 만들고 기능을 붙인다.

📌 화면을 가득 채우는 팁

position: fixed;
  left: 0px;
  right: 0px;
  top: 0px;
  bottom: 0px;

📌 이벤트 버블링

모달창을 만들면서, 이벤트핸들러를 연결한 X 버튼 외에 모달 박스를 클릭하여도 모달 창이 닫히는 문제가 발견 되었다.
이는 이벤트 버블링으로 인해 발생한 것이며, 이벤트 버블링이란 "자식 요소를 클릭했을 때, 부모 요소에 적용된 이벤트도 함께 실행되는 것"이다.

  • 해결 방법 1
    부모-자식 관계를 끊어준다. (복잡하다)
  • 해결 방법 2
    자식 요소에서 이벤트를 끊어주는 핸들러를 작성한다.
    onClick = ((event) => e.stopPropagation())

새로 배운 내용 - css⭐️

📌 position

postion은 요소의 위치를 결정하는 속성이며, relative & absolute를 이용하는 방법과 fixed를 이용하는 방법이 있다.

  • relative (부모) & absolute (자식)
    absolute를 사용하기 위해서는 기준점이 필요하다. 여기서의 기준점은 relative 속성을 가진 요소가 되고, 부모 요소를 거슬러 올라가면서 최상위에도 relative 속성을 가진 요소가 찾지 못할 경우에는 body를 absolute의 기준점으로 삼는다.
    absolute 속성을 가진 요소의 배치는 왼쪽-위 or 오른쪽-아래를 기준으로 값을 주면 된다.
    absolute의 자식요소는 absolute 속성을 상속받기 때문에, 중복으로 absolute 속성을 부여하지 않아도 된다.
        position: absolute;
        left: 0px;
        top: 0px;
  • fixed
    absolute와 달리 기준점이 없다. 즉 부모 요소를 기준으로 배치되지 않는다.
    부모요소를 탈출하여 혼자 움직이기 때문에, 상단의 Navbar와 같이고정하고 싶은 요소에 사용한다.

📌 opacity vs. rgba

  • opacity로 불투명도를 조정하면, 해당 요소에 포함된 모든 요소들에게도 불투명도가 적용된다. (자식요소도 불투명해짐)
    → opacity가 필터를 씌우는 역할이라고 생각하면 쉽다.
  • background-color : rgba(n, n, n, 여기)에서 불투명도를 조절할 수 있다. opacity와 달리 선택한 요소에만 투명도가 설정된다.

작성 코드 - react

import { useState } from 'react';
import styled from 'styled-components';

export const ModalContainer = styled.div`
  display: flex;
  position: relative;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  height: 100%;
`;

export const ModalBackdrop = styled.div` 
  background-color: rgba(0, 0, 0, 0.4);
  position: absolute;
  left: 0px;
  top: 0px;
  display: flex;
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
`;

export const ModalBtn = styled.button`
  background-color: var(--coz-purple-600);
  text-decoration: none;
  border: none;
  padding: 20px;
  color: white;
  border-radius: 30px;
  cursor: grab;
  
`;

export const ModalView = styled.div.attrs((props) => ({
  role: 'dialog',
}))` 
  background-color: white;
  width: 300px;
  height: 150px;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  border-radius: 30px;
  >button {
    background: none;
    color: black;
    font-size: 1rem;
    border: none;
  }
`;

export const Modal = () => {
  const [isOpen, setIsOpen] = useState(false);

  const openModalHandler = () => {
    setIsOpen(!isOpen);
  };

  return (
    <>
      <ModalContainer>
        <ModalBtn
          onClick={openModalHandler}
        >
          {isOpen ? "Opened!" : "Open Modal"}
        </ModalBtn>
        {isOpen ? (
          <>
            <ModalBackdrop onClick={openModalHandler} >
              <ModalView onClick={((e) => e.stopPropagation())} >
                <button onClick={openModalHandler}> X </button>
                <h2 className='text'>HELLO CODSTATES!</h2>
              </ModalView>
            </ModalBackdrop>
          </>
        )
          : null}
      </ModalContainer>
    </>
  );
};

작성 코드 - storybook

import React from 'react';
import '../variables.css';
import { Modal } from '../components/BareMinimumRequirements/Modal';

export default {
  title: 'Example/Modal',
  component: Modal
};

const Template = (args) => <Modal {...args} />;

export const Primary = Template.bind({});
Primary.args = {
  primary: true,
  label: 'Modal'
};

0개의 댓글