1. 헤더 구현
아직 대략적인 레이아웃과 로고, 2개의 링크, 로그인/회원가입 버튼만 헤더에 구현 해놓았다.
추후에 추가적으로 상품 카테고리 링크를 더 구성할 것이고 스크롤 했을 시에 헤더도 따라가게끔 할 예정이다.
현재 코드는 아래와 같다.
src/common/components/Navbar.tsx
import React, { useState } from 'react';
import { Link } from 'react-router-dom';
import styled from 'styled-components';
import { FiShoppingBag } from 'react-icons/fi';
import Button from './Button';
import Modal from './Modal';
const Navbar: React.FC = () => {
const [show, setShow] = useState(false);
const showHandler = () => {
setShow(!show);
};
return (
<>
<Modal show={show} click={showHandler} />
<Header>
<HomeLink to='/'>
<Logo />
<h1>쇼핑마켓</h1>
</HomeLink>
<nav>
<Link to='/clothes'>의류</Link>
<Link to='/electronics'>전자제품</Link>
<Button
text={'로그인/회원가입'}
textsize={'18px'}
color={'#fff'}
background={'blueviolet'}
click={showHandler}
/>
</nav>
</Header>
</>
);
};
로그인/회원가입 버튼을 누르면 모달이 열리게 하기 위해서 Navbar 컴포넌트에서 state를 만들고 eventHandler를 만든 후 Button과 Modal 컴포넌트에 eventHandler를 전달해주었다.
const Header = styled.header`
height: 80px;
display: flex;
justify-content: space-between;
border-bottom: 1px solid lightgray;
padding: 8px;
& nav {
display: flex;
align-items: center;
gap: 8px;
font-size: 18px;
& a:visited {
color: black;
}
}
`;
const HomeLink = styled(Link)`
display: flex;
align-items: center;
color: blueviolet;
&:visited {
color: blueviolet;
}
`;
const Logo = styled(FiShoppingBag)`
font-size: 32px;
`;
스타일링은 styled-components로 하였다.
2. 로그인 / 회원가입 모달창 구현
src/common/Modal.tsx
import React, { useState } from 'react';
import ReactDOM from 'react-dom';
import styled from 'styled-components';
import { FiShoppingBag } from 'react-icons/fi';
import Button from './Button';
import Backdrop from './Backdrop';
type Props = {
show: boolean;
click: React.MouseEventHandler;
};
const Modal: React.FC<Props> = ({ show, click }) => {
return (
<>
{show && <Backdrop click={click} />}
{show && <ModalOverlay />}
</>
);
};
export default Modal;
function ModalOverlay() {
const [userId, setUserId] = useState('');
const [password, setPassword] = useState('');
const [signinMode, setSigninMode] = useState(true);
const chageModeHadler = () => {
setSigninMode(!signinMode);
};
const idChageHadler = (event: React.ChangeEvent<HTMLInputElement>) => {
setUserId(event.target.value);
};
const passwordChageHadler = (event: React.ChangeEvent<HTMLInputElement>) => {
setPassword(event.target.value);
};
const submitHandler = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
const body = {
userId,
password,
};
console.log(body);
};
const content = (
<ModalContainer>
<div className='modal-header'>
<div className='logo'>
<Logo />
<h1>쇼핑마켓</h1>
</div>
<h2>{signinMode ? '로그인' : '회원가입'}</h2>
</div>
<form onSubmit={submitHandler}>
<input
type='text'
onChange={idChageHadler}
value={userId}
placeholder='아이디'
/>
<input
type='password'
onChange={passwordChageHadler}
value={password}
placeholder='비밀번호'
/>
<div className='button-container'>
<Button
text={signinMode ? '로그인' : '회원가입'}
textsize={'18px'}
width={'300px'}
height={'50px'}
color={'#fff'}
bgcolor={'blueviolet'}
/>
</div>
</form>
<div className='change-mode'>
<p className='signup-text'>
{signinMode ? '계정이 없으신가요?' : '이미 가입하셨나요?'}
</p>
<Button
text={signinMode ? '회원가입' : '로그인'}
textsize={'17px'}
background={'none'}
click={chageModeHadler}
color={'blueviolet'}
weight={'bold'}
/>
</div>
</ModalContainer>
);
return ReactDOM.createPortal(
content,
document.getElementById('modal-hook') as HTMLElement,
);
}
const ModalContainer = styled.div`
width: 400px;
height: 500px;
position: fixed;
z-index: 100;
top: 10vh;
left: 50vh;
background-color: #fff;
border-radius: 4px;
& .modal-header {
display: flex;
flex-direction: column;
align-items: center;
& .logo {
color: blueviolet;
display: flex;
align-items: center;
}
& h2 {
margin-top: 8px;
}
}
& .change-mode {
display: flex;
justify-content: center;
align-items: center;
& .signup-text {
font-size: 17px;
color: gray;
}
}
& form {
margin-top: 16px;
padding: 16px;
display: flex;
flex-direction: column;
gap: 6px;
& input {
height: 50px;
}
& .button-container {
display: flex;
justify-content: center;
margin-top: 16px;
}
}
`;
const Logo = styled(FiShoppingBag)`
font-size: 32px;
`;
src/common/Backdrop.tsx
import React from 'react';
import ReactDOM from 'react-dom';
import styled from 'styled-components';
type Props = {
click: React.MouseEventHandler;
};
const Backdrop: React.FC<Props> = ({ click }) => {
return ReactDOM.createPortal(
<BackGround onClick={click}></BackGround>,
document.getElementById('backdrop-hook') as HTMLElement,
);
};
export default Backdrop;
const BackGround = styled.div`
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100vh;
background: rgba(117, 117, 117, 0.45);
z-index: 10;
`;
HTML요소의 의미적이나 구조적인 관점에서 모달과 백드랍이 모든 영역 위에 깔린 것인지 알지 못하는 문제와 모달과 백드랍을 다른 돔으로 옮겨 CSS 상속 구조에서 벗어나기 위해 React potal을 사용했고
모달을 열고 닫을 때 모달뿐만 아니라 모달 뒤에 회색 배경도 나와야 하기 때문에 모달창을 ModalOveray라는 컴포넌트로 배경은 Backdrop 이라는 컴포넌트로 따로 만들고 Modal 컴포넌트에 함께 import 시켰다.
'프로젝트 개발 일지 > 쇼핑마켓 프론트엔드' 카테고리의 다른 글
쇼핑마켓- [프론트엔드]검색 기능 구현 (0) | 2023.08.20 |
---|---|
쇼핑마켓- [프론트엔드]redux-toolkit으로 products slice 구현 (0) | 2023.08.13 |
쇼핑마켓- [프론트엔드]의류 상품 목록 조회 페이지 구현 (0) | 2023.08.07 |