* React, Styled-Component, Storybook을 활용한 UI 컴포넌트 개발
* Styled Components를 활용해 Modal 커스텀 컴포넌트를 구현
* Storybook을 사용한 컴포넌트 관리
Modal Component
완성 예시
Modal UI 컴포넌트는 기존의 브라우저 페이지 위에 새로운 윈도우 창이 아닌, 레이어를 까는 것을 말한다.
팝업 창: 현재 열려있는 브라우저 페이지에 또 다른 브라우저 페이지를 띄우는 것
(브라우저에서 이 창을 열고 닫기 제어가 가능)
모달 창: 현재 열려있는 브라우저 페이지에 레이어를 까는 것
(기존의 페이지와 부모-자식의 관계로 브라우저의 새 창 제어 옵션에 전혀 영향을 받지 않는다.)
-> 팝업 창은 기존의 웹 페이지와 분리된 상태에서 작업을 하므로 재활용이 가능하며, 페이지별 이동이 자유롭다.
-> 따라서 팝업 창과 모달 창의 장점만을 가져오기 위해서는, iframe을 통해 모달 창 생성을 하면 된다.
예: window.open("popup.html", "", "width=300, height=300");
필요한 컴포넌트
ModalContainer (div) : Modal을 구현하는데 필요한 컴포넌트를 감싸주는 컨테이너
- ModalBtn (button) : Modal 창을 켜고 끌 수 있는 버튼
- ModalBackdrop (div) : Modal이 떴을 때의 배경
- ModalView (div) : Modal 창
- ExitBtn (button) : Modal 창 내부에 있는 X 버튼
- ModalView (div) : Modal 창
state
Modal 컴포넌트는 아래와 같은 state가 존재한다.
- isOpen state : 모달 창의 열고 닫힘 여부를 확인
모달 창 제어를 위한 핸들러 함수 openModalHandler는 모달 창을 여는 버튼, 모달 창의 X 버튼, 모달 창 바깥 배경 레이어에 넣어준다.
- 즉, 모달 창 버튼을 클릭해서 모달 창을 열고 모달 창 바깥 배경 혹은 모달 창 내부의 X버튼을 눌러 닫는다.
- openModalHandler 함수 :
- ModalBtn 클릭 시 발생되는 change 이벤트 핸들러
- 클릭할 때마다 상태가 Boolean 값으로 변경
조건부 렌더링
ModalBtn을 클릭하면 Modal이 열린 상태(isOpen)를 boolean 타입으로 변경하는 메서드가 실행되어야 한다.
- 조건부 렌더링을 활용해서 Modal이 열린 상태(isOpen이 true인 상태)일 때만
- 모달창(ModalView)과
- 배경(ModalBackdrop)이 뜰 수 있게 구현한다.
- 조건부 렌더링을 활용해서
- Modal이 열린 상태(isOpen이 true인 상태)일 때는 ModalBtn의 내부 텍스트가 'Opened!'로
- Modal이 닫힌 상태(isOpen이 false인 상태)일 때는 ModalBtn의 내부 텍스트가 'Open Modal'이 되도록 구현한다.
구현 코드
Modal UI Component 코드
import { useState } from 'react';
import styled from 'styled-components';
export const ModalContainer = styled.div`
height: 100%;
width: 100%;
display: flex;
justify-content: center;
align-items: center;
`;
// Modal이 떴을 때의 배경
export const ModalBackdrop = styled.div`
background-color: rgba(0,0,0,0.2); // 투명도 0.2
position: fixed; // 위치 고정
top : 0; // 화면을 꽉 채움
left : 0;
right : 0;
bottom : 0;
display: flex; // 자식요소인 모달 창 중앙으로 배치
justify-content: center;
align-items: center;
`;
export const ModalBtn = styled.button`
background-color: var(--coz-purple-500);
text-decoration: none;
border: none;
padding: 20px;
color: white;
border-radius: 30px;
cursor: grab; // 버튼에 마우스를 갖다대면 커서 아이콘 변경
`;
// 모달창 -> attrs 메소드를 이용해서 아래와 같이 div 엘리먼트에 속성을 추가할 수 있음
// (시맨틱한 마크업을 위해 role을 사용)
export const ModalView = styled.div.attrs((props) => ({
role: 'dialog',
}))`
height: 200px;
width: 300px;
background-color: skyblue;
border-radius: 10px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
position: relative;
> div.msg { // ModalView의 자식요소 CSS
font-size: 1.7rem;
color : #475ed4;
}
`;
// Styled Components로 만든 컴포넌트를 재사용 하려면 styled(가져올 컴포넌트 이름)으로 작성
export const ExitBtn = styled(ModalBtn)`
width: 5px;
height: 5px;
position: absolute;
right: 10px;
top: 10px;
display: flex;
justify-content: center;
align-items: center;
`
export const Modal = () => {
const [isOpen, setIsOpen] = useState(false);
const openModalHandler = () => {
setIsOpen(!isOpen);
};
return (
<>
<ModalContainer>
<!-- (1) 조건부 렌더링을 활용해서 Modal이 열린 상태(isOpen이 true인 상태)일 때는
ModalBtn의 내부 텍스트가 'Opened!' 로 Modal이 닫힌 상태(isOpen이 false인 상태)일 때는
ModalBtn 의 내부 텍스트가 'Open Modal'이 된다. -->
<ModalBtn onClick={openModalHandler}>
{ isOpen ? "Opened" : "Open Modal" }
</ModalBtn>
<!-- (2) 조건부 렌더링을 활용해서 Modal이 열린 상태(isOpen이 true인 상태)일 때만 모달창과 배경이 뜬다. -->
{isOpen ?
<ModalBackdrop onClick={openModalHandler}>
<ModalView onClick={(e) => e.stopPropagation()}>
<Exit onClick={openModalHandler}>
×
</Exit>
<div className = "msg">
Have a nice day 🤍
</div>
</ModalView>
</ModalBackdrop>
: null }
</ModalContainer>
</>
);
};
포인트
// 예시 코드
<ModalBackdrop onClick={openModalHandler}>
<ModalView>
<div> 모달창이 떴습니다! </div>
</ModalView>
</ModalBackdrop>
🟣 이벤트 버블링 :
- 모달창을 눌렀을 때는 모달창이 닫히면 안 된다.
- 위의 예시 코드에서 모달창(ModalView) 컴포넌트에 onClick 이벤트가 없음에도 불구하고, 해당 코드를 실행시켜보면 모달창만 눌러도 모달창이 닫힌다.
- 그 이유는, 모달창의 배경 레이어인 ModalBackdrop이 눌렀기 때문이며, 이를 이벤트 버블링이라고 한다.
- 이벤트 버블링(Bubbling)이란, 하위요소에서 상위요소로 이벤트(이 경우, 마우스로 클릭..!!)가 전파되어가는 방식이다.
- 자식 요소에 발생한 이벤트가 상위의 부모요소까지 영향을 미치는 것이다.
- 버블링을 막기 위해 event.stopPropagation()을 Click 이벤트로 설정해준다.
- 이벤트 버블링은 target요소에서 상위로 올라가 html 태그, document, window까지 전달된다.
- 이를 막기 위한 메소드 event.stopPropagation을 이벤트 객체에서 꺼내 쓴다.
- 참고로, 버블링의 반대는 캡쳐링(상위 부모요소로부터 하위로)이며 이를 막기 위한 메소드도 동일하다.
<ModalView onClick={(event) => event.stopPropagation()}>
🟣 attrs 메소드 : 엘리먼트에 속성을 추가
export const ModalView = styled.div.attrs((props) => ({
role: 'dialog',
}))
- 아래와 같이 div에 role 속성이 추가된다.
- 아래와 같이 컴포넌트 내에 속성을 추가한 것과 동일하다.
<ModalView name="anotherOne" onClick={(e) => e.stopPropagation()}>
심플 코드 (참고용)
더보기
위의 코드와 차이점은 isOpen의 값에 따라서 아래 코드는 True일 때는 모달창만, False일 때는 버튼만 구현했다.
(위의 코드는 isOpen 값이 True이면 모달창 + Opened 버튼 + 배경 레이어 / False일 때는 Open 버튼)
import React, { useState } from 'react';
import styled from 'styled-components';
export const Container = styled.div`
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
`;
export const ModalEl = styled.div`
width: 300px;
height: 300px;
background-color: blue;
text-align: center;
padding: 15px;
border-radius: 20px;
`;
export const Button = styled.button`
background-color: blue;
color: white;
border: none;
border-radius: 15px;
padding: 10px 20px;
cursor: pointer;
`;
export const ExitButton = styled(Button)`
background-color: white;
color: blue;
padding: 5px 10px;
margin-bottom: 85px;
`;
export const Text = styled.h1`
color: white;
font-size: 40px;
font-weight: bold;
`;
export const Modal = () => {
const [isOpen, setIsOpen] = useState(false);
const handleClick = () => {
setIsOpen(!isOpen); // 매 클릭마다 기존 state의 반대 값을 반복적으로 설정
};
return (
<Container>
{isOpen ? (
<ModalEl>
<ExitButton onClick={handleClick}>X</ExitButton>
<Text>Have a good day :) </Text>
</ModalEl>
) : <Button onClick={handleClick}>Open</Button> }
</Container>
);
}
Storybook 컴포넌트 관리
카테고리 관리
import React from 'react';
import '../variables.css';
import { Modal } from '../components/BareMinimumRequirements/Modal';
export default {
title: 'UI/Modal',
component: Modal,
argTypes: {
title: { control: "text" },
color: {control: "color"}
}
};
- UI/ Modal로 모달창 컴포넌트가 정리되어 들어가 있다.
아래와 같이 Storybook에서 색감 조정하기
직접 props를 받을 수 있도록 코드를 작성한다. (storybook 포스팅 참고)
Modal.stories.js
const Template = (args) => <Modal {...args} />;
export const Primary = Template.bind({});
Primary.args = {
primary: true,
label: 'Modal'
};
export const Practice = (args) =>{
return <Modal {...args} />
}
Modal.js
// 모달 버튼 바탕색 props.color 설정
export const ModalBtn = styled.div`
background-color: ${(props) => props.color || "var(--coz-purple-500)"};
.
.
.
`
.
.
.
// 모달 버튼 color 속성 추가
<ModalBtn color={color} onClick={openModalHandler}>
{ isOpen ? "Opened" : "Open Modal" }
</ModalBtn>
728x90
'FE > React' 카테고리의 다른 글
Styled Components로 Tab 만들기 (0) | 2023.04.20 |
---|---|
Styled Components로 Toggle 만들기 (0) | 2023.04.20 |
React Custom Component 개요 (0) | 2023.04.19 |
DOM reference를 잘 활용할 수 있는 useRef (0) | 2023.04.18 |
[React] StateAirline Client (2) : Effect Hook (0) | 2023.04.03 |