S3-U3) React Custom Component

강병규·2023년 2월 22일
0

모달 기능을 활용해 페이지 위에 새로운 창을 띄워 콘텐츠를 보여줄 수 있다. 모달은 일반 팝업창과 다르게 이용자가 버튼 또는 링크를 클릭했을 때만 동작하며 모달을 닫기 전까지는 기존 화면과 상호작용할 수 없다.

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

export const ModalContainer = styled.div` //기본적으로 해당 모달 컴포넌트의 상위 요소
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100%;
`;

export const ModalBackdrop = styled.div` //=> 화면에 구현되는 ModalView의 배경으로 ModalView를 포함함
  // TODO : Modal이 떴을 때의 배경을 깔아주는 CSS를 구현합니다.
  background: rgba(0, 0, 0, 0.5);
  position: fixed;
  left: 0;
  right: 0;
  top: 0;
  bottom: 0;
  display: flex;
  justify-content: center;
  align-items: center;
`;

export const ModalBtn = styled.button`   // ModalBtn 버튼을 클릭하면 ModalView와 ModalBackdrop가 화면에 구현이 된다.
  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) => ({
  // attrs 메소드를 이용해서 아래와 같이 div 엘리먼트에 속성을 추가할 수 있습니다.
  role: "dialog",
}))`
  // TODO : Modal창 CSS를 구현합니다.
  background-color: white;
  width: 300px;
  height: 150px;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
`;

export const Modal = () => {
  const [isOpen, setIsOpen] = useState(false);
//=> isOpen을 통해 열리고 닫고를 정한다
  const openModalHandler = () => {
    // TODO : isOpen의 상태를 변경하는 메소드를 구현합니다.
    setIsOpen(!isOpen);
  };
//=> openModalHandler 함수를 통해 isOpen의 boolean 값을 조절한다.
  return (
    <>
      <ModalContainer>
        <ModalBtn onClick={openModalHandler}>
          {isOpen ? "opened" : "plz click"}
        </ModalBtn>
        {isOpen ? (
          // 모달 배경화면을 누르면 setIsOpen(!isOpen)가 된다.
          <ModalBackdrop onClick={openModalHandler}>
            <ModalView>
              <div className="close-btn">&times;</div>모달
            </ModalView>
          </ModalBackdrop>
        ) : null}
      </ModalContainer>
    </>
  );
};

Tab

탭은 인터페이스 요소 중 하나이며, 탭 인터페이스가 적용된 경우 한 창에 여러 문서를 열 수 있으며, 한 창에 한 탭만 표시하여 사용자가 현재 어떤 문서를 보고 있는지 알 수 있다.

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



const TabMenu = styled.ul`
  background-color: #dcdcdc;
  color: rgba(73, 73, 73, 0.5);
  font-weight: bold;
  display: flex;

  flex-direction: row;
  justify-content: center;
  align-items: center;
  list-style: none;
  margin-bottom: 7rem;

  .submenu {
    width: 200px;
    height: 50px;
    display: flex;
    justify-content: center;
    align-items: center;
    background: #f2f2f2;
    border: 1px solid #424448;
    cursor: pointer;
  }

  .focused {
    background: #cffc;
  }

  & div.desc {
    text-align: center;
  }
`;

const Desc = styled.div`
  text-align: center;
`;

export const Tab = () => {
  // TIP: Tab Menu 중 현재 어떤 Tab이 선택되어 있는지 확인하기 위한
  // currentTab 상태와 currentTab을 갱신하는 함수가 존재해야 하고, 초기값은 0 입니다.

  const [currentTab, changeTab] = useState(0);
//=> currentTab은 현재 선택된 탭을 표시한다.
  const menuArr = [
    { name: "Tab1", content: "Tab menu ONE" },
    { name: "Tab2", content: "Tab menu TWO" },
    { name: "Tab3", content: "Tab menu THREE" },
  ];
  //=> 배열에 각탭의 name과 content를 저장함

  const selectMenuHandler = (index) => {
    changeTab(index);
    
  };
//=> 배열의 인덱스를 통해 섹션을 구분함
  return (
    <>
      <div>
        <TabMenu>
          //=> map을 통해 menuArr을 li로 변환한다.
          {menuArr.map((ele, index) => {
            return (
              <li
                key={index}
                className={currentTab === index ? "submenu focused" : "submenu"}
                onClick={() => selectMenuHandler(index)}
              >
                {ele.name}
              </li>
            );
          })}

          {/*TIP: li 엘리먼트의 class명의 경우 선택된 tab 은 'submenu focused' 가 되며, 
                  나머지 2개의 tab은 'submenu' 가 됩니다.*/}
        </TabMenu>
        <Desc>
          {/*TODO: 아래 하드코딩된 내용 대신에, 현재 선택된 메뉴 따른 content를 표시하세요*/}
          <h1>{menuArr[currentTab].content}</h1>
        </Desc>
      </div>
    </>
  );
};

Toggle

on/off를 설정할 때 사용하는 버튼으로 사용자가 on/off시 변화하는 시각적인 효과를 통해 on/off 여부를 알 수 있게 한다.


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

const ToggleContainer = styled.div` //
  position: relative;
  margin-top: 8rem;
  left: 47%;
  cursor: pointer;

  > .toggle-container { //=> 토글 스위치버튼을 담은 본체
    width: 50px;
    height: 24px;
    border-radius: 30px;
    background-color: #8b8b8b;
    // TODO : .toggle--checked 클래스가 활성화 되었을 경우의 CSS를 구현합니다.
  }
  > .toggle-container.toggle--checked {
    background: #fcf;
  }

  > .toggle-circle { //=> 시각적 변화를 통해 ON/OFF를 알려주는 버튼모양
    position: absolute;
    top: 1px;
    left: 1px;
    width: 22px;
    height: 22px;
    border-radius: 50%;
    background-color: #ffffff;
    // TODO : .toggle--checked 클래스가 활성화 되었을 경우의 CSS를 구현합니다.
  }

  > .toggle-circle.toggle--checked {
    left: 26px;
  }
`;

const Desc = styled.div`
  // TODO : 설명 부분의 CSS를 구현합니다.
`;

export const Toggle = () => {
  const [isOn, setisOn] = useState(false);
//=> isOn을 통해 on,off 여부를 정함
  const toggleHandler = () => {
    setisOn(!isOn);boolean 값을 조절한다.
    // toggleHandler함수를 통해 isOn의 boolean 값을 조절한다.
  };

  return (
    <>
      <ToggleContainer
        // TODO : 클릭하면 토글이 켜진 상태(isOn)를 boolean 타입으로 변경하는 메소드가 실행되어야 합니다.
        onClick={toggleHandler}
      >
        {/* TODO : 아래에 div 엘리먼트 2개가 있습니다. 각각의 클래스를 'toggle-container', 'toggle-circle' 로 지정하세요. */}
        {/* TIP : Toggle Switch가 ON인 상태일 경우에만 toggle--checked 클래스를 div 엘리먼트 2개에 모두 추가합니다. 조건부 스타일링을 활용하세요. */}
        <div className={`toggle-container ${isOn ? "toggle--checked" : ""}`} />
        <div className={`toggle-circle ${isOn ? "toggle--checked" : ""}`} />
      </ToggleContainer>
      <Desc>토글 스위치 {isOn ? "ON" : "OFF"}</Desc>
      {/* TIP:  Toggle Switch가 ON인 상태일 경우에 Desc 컴포넌트 내부의 텍스트를 'Toggle Switch ON'으로, 그렇지 않은 경우 'Toggle Switch OFF'가 됩니다. 조건부 렌더링을 활용하세요. */}
    </>
  );
};

Tag

블로그나 게시판에서 게시물을 작성 시 해당 게시물의 특징을 나타낼 수 있는 키워드를 붙이는 기능 이러한 태그는 게시물의 분류에 사용하거나, 검색어로서 기능을 수행한다


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



export const TagsInput = styled.div`
  margin: 8rem auto;
  display: flex;
  align-items: flex-start;
  flex-wrap: wrap;
  min-height: 48px;
  width: 480px;
  padding: 0 8px;
  border: 1px solid rgb(214, 216, 218);
  border-radius: 6px;

  > ul {
    display: flex;
    flex-wrap: wrap;
    padding: 0;
    margin: 8px 0 0 0;

    > .tag {
      width: auto;
      height: 32px;
      display: flex;
      align-items: center;
      justify-content: center;
      color: #fff;
      padding: 0 8px;
      font-size: 14px;
      list-style: none;
      border-radius: 6px;
      margin: 0 8px 8px 0;
      background: var(--coz-purple-600);
      >.tag-title{
      color:white
      	//=> 태그 글자색
      
      }
      > .tag-close-icon {
        display: block;
        width: 16px;
        height: 16px;
        line-height: 16px;
        text-align: center;
        font-size: 14px;
        margin-left: 8px;
        color: var(--coz-purple-600);
        border-radius: 50%;
        background: #fff;
        cursor: pointer;
        
        // 태그를 없애는 버튼아이콘
      }
    }
  }

  > input {
    flex: 1;
    border: none;
    height: 46px;
    font-size: 14px;
    padding: 4px 0 0 0;
    :focus {
      outline: transparent;
    }
  }

  &:focus-within {
    border: 1px solid var(--coz-purple-600);
  }
`;

export const Tag = () => {
  const initialTags = ["CodeStates", "kimcoding"];// 태그 초기값

  const [tags, setTags] = useState(initialTags);
  const [value, setValue] = useState();
  const removeTags = (indexToRemove) => {
    // TODO : 태그를 삭제하는 메소드를 완성하세요.
    setTags(tags.filter((ele, i) => i !== indexToRemove));
  };

  const addTags = (event) => {
    // TODO : tags 배열에 새로운 태그를 추가하는 메소드를 완성하세요.
    // 이 메소드는 태그 추가 외에도 아래 3 가지 기능을 수행할 수 있어야 합니다.
    // - 이미 입력되어 있는 태그인지 검사하여 이미 있는 태그라면 추가하지 말기
    // - 아무것도 입력하지 않은 채 Enter 키 입력시 메소드 실행하지 말기
    // - 태그가 추가되면 input 창 비우기
    if (event.key === "Enter" && !tags.includes(value) && value) {
      setTags([...tags, value]);
      setValue();
    } else if (event.key === "Enter" && !value) {
      setValue();
    }
  };

  const changeHandler = (ele) => {
    setValue(ele.target.value);
  };

  return (
    <>
      <TagsInput>
        <ul id="tags">
          {tags.map((tag, index) => (
            <li key={index} className="tag">
              <span className="tag-title">{tag}</span>
              <span
                onClick={() => removeTags(index)}
                className="tag-close-icon"
              >X
                {/* TODO :  tag-close-icon이 tag-title 오른쪽에 x 로 표시되도록 하고,
                            삭제 아이콘을 click 했을 때 removeTags 메소드가 실행되어야 합니다. */}
              </span>
            </li>
          ))}
        </ul>
        <input
          className="tag-input"
          type="text"
          onKeyUp={(event) => addTags(event)}
          onChange={changeHandler}
          value={value || ""}
          placeholder="Press enter to add tags"
        />
      </TagsInput>
    </>
  );
};
profile
ㅇㅅㅇ

0개의 댓글