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>
        &nbsp;
        <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 시켰다.

 

 

+ Recent posts