항해 24일차 (Btn, Input, Modal, Select)

Undong·2023년 4월 27일
0

항해구구

목록 보기
24/52
post-thumbnail

항해 24일차

오늘은 과제를 통한 코드를 리뷰해보겠다..

과제를 보면

Button, Input, Modal, Select를 이용하여 기능을 구현한 프로젝트이다.

각 컴포넌트에 대해 설명하겠다.

Btn

import React from "react";
import styled from "styled-components";

const StHeader = styled.div`
  display: flex;
  flex-direction: row;
  gap: 10px;
  margin-bottom: 10px;
`;

const Stbtn = styled.button`
  cursor: pointer;
  border-radius: 8px;
  color: ${(props) => props.color};
  height: ${(props) => props.height};
  width: ${(props) => props.width};
  border: 3px solid ${(props) => props.bordercolor};
  background-color: ${(props) => props.background};
  font-weight: 600;
  &:active {
    opacity: 0.5;
  }
`;

function Btn() {
  return (
    <div>
      <h1>Button</h1>
      <StHeader>
        <Stbtn
          bordercolor="rgb(85, 239, 196)"
          background="white"
          width="200px"
          height="50px"
          onClick={() => {
            alert("버튼을 만들어보세요");
          }}
        >
          Large Primary Button 〉
        </Stbtn>
        <Stbtn
          background="rgb(85, 239, 196)"
          bordercolor="rgb(85, 239, 196)"
          width="130px"
          height="45px"
        >
          Medium
        </Stbtn>
        <Stbtn
          background="rgb(85, 239, 196)"
          bordercolor="rgb(85, 239, 196)"
          width="100px"
          height="40px"
        >
          small
        </Stbtn>
      </StHeader>
      <StHeader>
        <Stbtn
          bordercolor="rgb(214, 48, 49)"
          background="white"
          width="200px"
          height="50px"
          onClick={() => {
            prompt("어렵나요?");
          }}
        >
          Large Primary Button 🔔
        </Stbtn>
        <Stbtn
          bordercolor="rgb(214, 48, 49)"
          background="rgb(214, 48, 49)"
          width="130px"
          height="45px"
        >
          Medium
        </Stbtn>
        <Stbtn
          bordercolor="rgb(214, 48, 49)"
          background="rgb(214, 48, 49)"
          width="100px"
          height="40px"
        >
          small
        </Stbtn>
      </StHeader>
    </div>
  );
}

export default Btn;

Btn 컴포넌트에서는 StHeader와 Stbtn을 사용하여 버튼 UI를 구성합니다. StHeader를 사용하여 버튼들을 각각 그룹화하고, Stbtn을 사용하여 버튼들을 구성합니다.
각 Stbtn 컴포넌트는 bordercolor, background, width, height 등의 속성을 props로 받습니다. 이를 사용하여 각 버튼의 스타일을 동적으로 조정할 수 있습니다. 예를 들어, 첫 번째 버튼은 background와 bordercolor를 다르게 설정하여 다른 버튼들과 구분됩니다.
버튼 클릭 시 onClick 이벤트 핸들러를 사용하여 alert()나 prompt()을 띄웁니다. 첫 번째 버튼은 alert()를 사용하여 경고 메시지를 띄우고, 두 번째 버튼은 prompt()를 사용하여 질문을 띄웁니다.

input

import React from "react";
import { useState } from "react";
import styled from "styled-components";

const StHeader = styled.div`
  display: flex;
  flex-direction: row;
  gap: 10px;
  margin-bottom: 10px;
`;

const Stinput = styled.input`
  border: 1px solid rgb(51, 51, 51);
  height: 40px;
  width: 200px;
  outline: none;
  border-radius: 8px;
  padding-left: 12px;
  padding-right: 12px;
`;

const Stbtn = styled.button`
  cursor: pointer;
  border-radius: 8px;
  color: ${(props) => props.color};
  height: ${(props) => props.height};
  width: ${(props) => props.width};
  border: 3px solid ${(props) => props.bordercolor};
  background-color: ${(props) => props.background};
  font-weight: 600;
  &:active {
    opacity: 0.5;
  }
`;

function Input() {
  const [price, setPrice] = useState(0);
  const [name, setName] = useState("");

  const handlenamechange = (e) => {
    setName(e.target.value); // 이름 받아오기
  };

  const handlePriceChange = (event) => {
    // 가격 받아오기
    const value = event.target.value.replace(/[^0-9]/g, ""); // 숫자 이외의 값은 모두 제거
    setPrice(value);
  };

  const formatPrice = (price) => {
    // 숫자를 넣었을 때, 3자리 수마다 콤마 ,가 찍히는 input
    return price.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","); // /로 시작하고 /로 끝나는 것이 특징이며, \B는 단어 경계가 아닌 곳을 나타냅니다. 즉, 숫자 사이의 위치를 나타냅니다. (?=로 시작하는 것은 전방 탐색(lookahead assertion)을 나타내며, \d{3}은 3개의 숫자를 나타냅니다. +는 하나 이상의 반복을 의미합니다. (?!는 후방 부정형 전방 탐색(negative lookahead)을 나타내며, \d는 숫자를 의미합니다. 따라서 (?!\d)는 숫자 다음에 나오는 것이 아닌 경우를 나타냅니다.
  };

  const handleSave = (name, price) => {
    // 버튼 누르면 { name: 입력 값, price: 입력 값 }

    alert(`{name: ${name} price: ${price}}`);
  };

  return (
    <div>
      <h1>Input</h1>
      <form>
        <StHeader>
          <div>
            <label>이름</label>
            <Stinput type="text" value={name} onChange={handlenamechange} />
          </div>
          <div>
            <label>가격</label>
            <Stinput
              type="text"
              value={formatPrice(price)}
              onChange={handlePriceChange}
            />
          </div>
          <div>
            <Stbtn
              background="rgb(85, 239, 196)"
              bordercolor="rgb(85, 239, 196)"
              width="100px"
              height="40px"
              onClick={() => handleSave(name, price)}
            >
              저장
            </Stbtn>
          </div>
        </StHeader>
      </form>
    </div>
  );
}

export default Input;

이 컴포넌트는 세 가지 상태를 관리합니다. 첫 번째 상태는 name이라는 문자열 상태이며, 두 번째 상태는 price라는 숫자 상태입니다. 세 번째 상태는 formatPrice라는 함수입니다. 이 함수는 숫자를 입력 받아, 3자리마다 콤마(,)를 추가하는 역할을 합니다.
컴포넌트는 입력 폼과 버튼으로 구성됩니다. 입력 폼에는 이름과 가격을 입력할 수 있습니다. 이름은 Stinput 요소로 입력하며, 가격은 Stinput 요소에 formatPrice 함수를 적용하여 입력합니다. 저장 버튼을 클릭하면 handleSave 함수가 실행됩니다. 이 함수는 입력한 이름과 가격을 포함한 객체를 알림 창으로 출력합니다.

import React from "react";
import styled from "styled-components";

const Stlayout = styled.div`
  width: 100%;
  height: 100vh;
  inset: 0px;
  opacity: 1;
  position: fixed;
  background-color: rgba(221, 221, 221, 0.8);
`;

const Stmodal = styled.div`
  border-radius: 12px;
  box-sizing: border-box;
  padding: 24px;
  background-color: white;
  width: 500px;
  height: 300px;
  position: absolute;
  top: 50%;
  opacity: 1;
  left: 50%;
  transform: translate(-50%, -50%);
`;

const Stbtn = styled.button`
  cursor: pointer;
  border-radius: 8px;
  color: ${(props) => props.color};
  height: ${(props) => props.height};
  width: ${(props) => props.width};
  border: 3px solid ${(props) => props.bordercolor};
  background-color: ${(props) => props.background};
  font-weight: 600;
  &:active {
    opacity: 0.5;
  }
`;

const StHeader = styled.div`
  display: flex;
  flex-direction: row;
  justify-content: center;
  align-items: center;
  margin-top: 200px;
  gap: 10px;
  margin-bottom: 10px;
`;

const Modal = ({ open, close, header, on }) => {
  // 열기, 닫기, 모달 헤더 텍스트를 부모로부터 받아옴
  return (
    // 모달이 열릴때 openModal 클래스가 생성된다.
    <div>
      {open ? (
        <Stlayout>
          <Stmodal>
            <header>{header}</header>
            <StHeader>
              <Stbtn
                background="rgb(214, 48, 49)"
                bordercolor="rgb(214, 48, 49)"
                width="100px"
                height="40px"
                className="close"
                onClick={close}
              >
                닫기
              </Stbtn>
              <Stbtn
                background="rgb(85, 239, 196)"
                bordercolor="rgb(85, 239, 196)"
                width="100px"
                height="40px"
                className="on"
                onClick={on}
              >
                확인
              </Stbtn>
            </StHeader>
          </Stmodal>
        </Stlayout>
      ) : null}
    </div>
  );
};

export default Modal;

모달이 열릴 때 open prop이 true가 되며, header prop은 모달의 제목으로 보여지게 됩니다. close와 on 함수는 부모 컴포넌트에서 전달되는 콜백 함수입니다. close 함수는 모달을 닫기 위한 함수이고, on 함수는 모달에서 발생하는 이벤트에 대한 처리를 위한 함수입니다.
모달은 open props가 true일 때만 렌더링되며, 모달의 버튼들은 onClick prop으로 전달된 콜백 함수를 실행하도록 되어 있습니다. close 함수는 모달을 닫습니다. on 함수는 모달에서 발생하는 이벤트에 대한 처리를 담당합니다

import React, { useRef } from "react";
import styled from "styled-components";

const Stlayout = styled.div`
  width: 100%;
  height: 100vh;
  inset: 0px;
  position: fixed;
  background-color: rgba(221, 221, 221, 0.8);
`;

const Stmodal = styled.div`
  border-radius: 12px;
  box-sizing: border-box;
  padding: 24px;
  background-color: white;
  width: 500px;
  height: 300px;
  position: absolute;
  top: 50%;
  opacity: 1;
  left: 50%;
  transform: translate(-50%, -50%);
`;

const Stbtn = styled.button`
  cursor: pointer;
  border-radius: 8px;
  color: ${(props) => props.color};
  height: ${(props) => props.height};
  width: ${(props) => props.width};
  border: 3px solid ${(props) => props.bordercolor};
  background-color: ${(props) => props.background};
  font-weight: 600;
  &:active {
    opacity: 0.5;
  }
`;

const StHeader = styled.div`
  display: flex;
  flex-direction: row;
  justify-content: center;
  align-items: center;
  margin-top: 200px;
  gap: 10px;
  margin-bottom: 10px;
`;


const Modal = ({ open, close, header }) => {
  const modalRef = useRef(); // Dom 요소 접근

  const handleOutsideClick = (e) => {
    if (modalRef.current && !modalRef.current.contains(e.target)) {
      // modalRef.current : 현재 참조하고 있는 Dom, modalRef.current.contains(e.target): 외부 클릭 true, 내부 클릭시 false
      close();
    }
  };

  return (
    <div onClick={handleOutsideClick}>
      {open ? (
        <Stlayout>
          <Stmodal ref={modalRef}>
            <header>{header}</header>
            <StHeader>
              <Stbtn
                background="rgb(214, 48, 49)"
                bordercolor="rgb(214, 48, 49)"
                width="100px"
                height="40px"
                className="close"
                onClick={close}
              >
                닫기
              </Stbtn>
            </StHeader>
          </Stmodal>
        </Stlayout>
      ) : null}
    </div>
  );
};

export default Modal;

modal과 동일하나, 컴포넌트 내부에서는 useRef()를 사용하여 모달 창의 DOM 요소를 참조하고, 이를 클릭 이벤트 처리에 사용합니다. handleOutsideClick() 함수는 e 이벤트 객체를 인자로 받아서 모달 창 외부를 클릭할 때, 모달 창이 닫히도록 close() 함수를 호출합니다. 이 때 modalRef.current.contains(e.target)를 사용하여, 클릭 이벤트가 모달 창 내부에서 발생한 것인지 외부에서 발생한 것인지 구분합니다.

import React from "react";
import { useState } from "react";
import styled from "styled-components";
const StSelectBtn = styled.button`
  width: 140px;
  height: 35px;
  border-radius: 12px;
  border: none;
`;

function Select({ option }) {
  const [contextMenuOpen, setContextMenuOpen] = useState(false); // 기본 값 닫혀 있음

  const [selectedItem, setSelectedItem] = useState(option[0]);
  // 버튼 아이템 기본 값
  const openContextMenu = () => {
    if (contextMenuOpen) {
      setContextMenuOpen(false);
      // true => false
    } else {
      // false => true
      setContextMenuOpen(true);
    }
  };

  const selectMenu = (item) => {
    setSelectedItem(item);
    setContextMenuOpen(false);
  };

  return (
    <div>
      <div>
        <StSelectBtn onClick={openContextMenu}>{selectedItem}</StSelectBtn>
        {contextMenuOpen && (
          <div>
            {option.map((item) => (
              <div>
                <StSelectBtn onClick={() => selectMenu(item)}>
                  {item}
                </StSelectBtn>
              </div>
            ))}
          </div>
        )}
      </div>
    </div>
  );
}

export default Select;

컴포넌트는 option이라는 속성을 입력으로 받습니다. 이 속성은 드롭다운 메뉴에 표시될 항목들을 배열로 포함합니다.
컴포넌트는 contextMenuOpen이라는 state 변수를 가지며, 이 값은 드롭다운 메뉴가 열려있는지 여부를 나타냅니다. contextMenuOpen의 기본값은 false입니다.
컴포넌트는 selectedItem이라는 state 변수를 가지며, 이 값은 현재 선택된 항목을 나타냅니다. selectedItem의 기본값은 option 배열의 첫 번째 항목입니다.
openContextMenu 함수는 드롭다운 메뉴를 열거나 닫습니다. 이 함수는 contextMenuOpen state 변수를 변경하여 열림/닫힘 상태를 변경합니다.
selectMenu 함수는 사용자가 메뉴에서 항목을 선택할 때 호출됩니다. 이 함수는 selectedItem state 변수를 선택한 항목으로 변경하고, contextMenuOpen state 변수를 닫힘으로 변경합니다.
컴포넌트는 StSelectBtn 스타일 컴포넌트를 사용하여 드롭다운 버튼과 메뉴 항목을 표시합니다. openContextMenu 함수가 드롭다운 버튼 클릭 시 호출되며, contextMenuOpen이 true이면 option 배열에서 항목을 렌더링합니다. 선택된 항목은 selectedItem에 의해 표시됩니다.

마무리

버튼,인풋,모달,셀렉트 기능들을 구현해봤고, 더욱 더 기능에 대해 공부할 수 있는 계기가 되었다. 또한 모달에 대해 css로 구현하는 방법과 React-Select라는 라이브러리로 select를 구현할 수 있다는 것을 알게 되어 한번 만들어봐야겠당🔥

profile
console.log("Hello")

0개의 댓글