React custom component 과제

햄은 개발 공부중·2023년 2월 22일
0
post-thumbnail

저번주 피그마 파트에서 배운 Modal, Toggle, Tab, Tag 를 이번주에 배운 CDD, styled Components, Storybook을 통해 React에서 UI 컴포넌트를 만드는 실습이었다!

Bare minimum Requirement

요구사항🤚

  • Styled Components 라이브러리를 활용해 ModalContainer ModalBackdrop ModalBtn ModalView 컴포넌트의 CSS를 자유롭게 구현합니다.
    • ModalContainer : Modal을 구현하는데 필요한 컴포넌트를 감싸주는 컨테이너 컴포넌트 역할을 합니다.
    • ModalBackdrop : Modal이 떴을 때의 배경을 깔아주는 역할을 합니다.
    • ModalBtn : Modal 창을 키고 끌 수 있는 버튼입니다.
    • ModalView : Modal 창 컴포넌트입니다.
  • 구현한 Styled Components들을 Modal 컴포넌트 내부에서 활용합니다.
    Modal 버튼 기능 테스트

코드

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`
  background-color: 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`
  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) => ({
  role: 'dialog',
}))`
  // TODO : Modal창 CSS를 구현합니다.
  background-color: white;
  width: 300px;
  height: 150px;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  border-radius: 30px;
  >button {
    background: none;
    color: black;
    font-size: 1rem;
    border: none;
  }
`;

export const Modal = () => {
  const [isOpen, setIsOpen] = useState(false);

  const openModalHandler = () => {
    // TODO : isOpen의 상태를 변경하는 메소드를 구현합니다.
    setIsOpen(!isOpen)
  };

  return (
    <>
      <ModalContainer>
        <ModalBtn onClick={openModalHandler}>
          Open Modal
        </ModalBtn>
        {isOpen? (
          <ModalBackdrop onClick={openModalHandler}>
            <ModalView onClick={e=>e.stopPropagation()}>
              <button>X</button>
              <div>HELLO CODESTATES!</div>
            </ModalView>
          </ModalBackdrop>
        ) : null}
      </ModalContainer>
    </>
  );
};

Toggle

요구사항🤚

  • Styled Components 라이브러리를 활용해 ToggleContainer Desc 컴포넌트의 CSS를 자유롭게 구현합니다.
    • ToggleContainer : Toggle을 구현하는데 필요한 컴포넌트를 감싸주는 컨테이너 컴포넌트 역할을 합니다.
    • Desc : Toggle Switch의 상태를 설명하는 텍스트를 담는 컴포넌트입니다.
  • ToggleContainer 내부에 .toggle-container .toggle-circle 클래스를 가진 div 요소를 각각 생성합니다.
  • 생성한 요소에 조건부 스타일링을 활용해 Toggle Switch가 ON인 상태일 경우에만 toggle--checked 클래스를 두 요소 모두에 추가합니다.
  • 기본 CSS에서는 템플릿 리터럴과 삼항 연산자를 활용해 조건부 스타일링을 적용할 수 있습니다.
<div className={`toggle-container ${isOn ? "toggle--checked" : ""}`} />
  • 조건부 렌더링을 활용해 Toggle Switch가 ON인 상태일 경우에 Desc 컴포넌트 내부의 텍스트를 'Toggle Switch ON' 으로 Toggle Switch가 OFF인 상태일 경우에는 'Toggle Switch OFF'로 변경합니다.
  • 토글 스위치가 부드럽게 옮겨지는 애니메이션 효과를 주기 위해서는 CSS의 transition 속성을 활용할 수 있습니다. 토글을 기능을 다 구현했다면 시도해 보세요!

코드

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;
    transition: 0.5s;

    &.toggle--checked {
      background-color: pulple;
      transition: 0.5s;

    }
  }

  > .toggle-circle {
    position: absolute;
    top: 1px;
    left: 1px;
    width: 22px;
    height: 22px;
    border-radius: 50%;
    background-color: #ffffff;
    transition: 0.5s;

    &.toggle--checked {
      left: 27px;
      transition: 0.5s;
    }
  }
`;

const Desc = styled.div`
  // TODO : 설명 부분의 CSS를 구현합니다.
  text-align: center;
  padding: 20px;
`;

export const Toggle = () => {
  const [isOn, setisOn] = useState(false);

  const toggleHandler = () => {
    setisOn(!isOn)
  };

  return (
    <>
      <ToggleContainer>
        <div onClick={toggleHandler} className={isOn? "toggle--checked toggle-container" : 'toggle-container'}/>
        <div onClick={toggleHandler} className={isOn? "toggle--checked toggle-circle" : 'toggle-circle'}/>
      </ToggleContainer>
      
      <Desc>{isOn? 'Toggle Switch ON' : 'Toggle Switch OFF'}</Desc>
    </>
  );
};

Tab

요구사항🤚

  • Styled Components 라이브러리를 활용해 TabMenu Desc 컴포넌트의 CSS를 자유롭게 구현합니다.
    • TabMenu : Tab을 구현하는데 필요한 컴포넌트를 감싸주는 컨테이너 컴포넌트 역할을 합니다.
    • Desc : Toggle Switch의 상태를 설명하는 텍스트를 담는 컴포넌트입니다.
  • TabMenu 내부에 .submenu 클래스명을 가진 li 요소들을 map 을 이용한 반복을 통해 생성합니다.
  • TabMenu 내부에 .submenu 클래스명을 가진 li 요소의 textContent 는 각 요소의 name 입니다.
  • 조건부 렌더링을 활용해서 Tab 메뉴가 선택된 상태일 때, 선택된 Tab 메뉴 li 요소의 클래스명만 submenu focused 가 되어야 하고, 선택되지 않은 나머지는 submenu 가 되도록 구현해야 합니다.
  • TabMenu 를 클릭하면 현재 선택된 탭의 인덱스 값을 전달받아 currentTab 상태를 변경하는 selectMenuHandler 메서드가 실행되어야 합니다.
  • TabMenu 를 클릭하면 현재 선택된 탭 메뉴만 .focused CSS가 적용되어야 합니다.
  • TabMenu 를 클릭하면 Desc 컴포넌트의 content 의 내용이 해당 탭의 content로 바뀌어야 합니다.

코드

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-items: center;
  align-items: center;
  list-style: none;
  margin-bottom: 7rem;

  .submenu {
    ${'' /* 기본 Tabmenu 에 대한 CSS를 구현합니다. */}
    padding: 10px;
    width: calc(100% / 3);
    transition: 0.5s;

  }

  .focused {
    ${'' /* 선택된 Tabmenu 에만 적용되는 CSS를 구현합니다.  */}
    background-color: var(--coz-purple-300);
    transition: 0.5s;
  }

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

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

export const Tab = () => {
  const [currentTab, setCurrentTab] = useState(0)

  const menuArr = [
    { name: 'Tab1', content: 'Tab menu ONE' },
    { name: 'Tab2', content: 'Tab menu TWO' },
    { name: 'Tab3', content: 'Tab menu THREE' },
  ];

  const selectMenuHandler = (index) => {
    setCurrentTab(index)
  };

  return (
    <>
      <div>
        <TabMenu>
          {menuArr.map((el, index) => <li key={index} onClick={()=>selectMenuHandler(index)} className={index === currentTab ? 'submenu focused': "submenu"}>{el.name}</li>)}
        </TabMenu>
        <Desc>
          <p>{menuArr[currentTab].content}</p>
        </Desc>
      </div>
    </>
  );
};

Tag

요구사항🤚

  • input 기능 테스트
    input 창에 텍스트를 입력 후 Enter 키를 누르면 태그가 추가되어야 합니다. 마우스 클릭이 아닌 Enter 키를 통해 태그가 추가되도록 하며, Enter 키가 눌리면 태그를 추가하는 addTags 메서드가 실행되어야 합니다.
    addTags 메서드는 기본적으로 태그를 추가하는 기능 이외에 아래 세 가지 기능도 수행할 수 있어야 합니다.
    이미 입력되어 있는 태그인지 검사하여 이미 입력되어 있다면 추가하지 말아야 합니다.
    아무것도 입력하지 않은 상태에서는 Enter 키를 눌러도 addTags 메서드가 실행되지 않아야 합니다.
    태그가 추가되고 나면 input 창이 비워져야 합니다.
    삭제 기능 테스트

기본적으로 tags 배열 안의 모든 태그들이 화면에 보여야 합니다.
태그 이름 옆에 삭제 아이콘(x)이 표시되도록 하고, 아이콘을 클릭하면 해당 태그를 삭제하는 removeTags 메서드가 실행되어야 합니다.
removeTags 메서드가 삭제 아이콘(x)이 눌린 태그를 삭제하도록 removeTags 메서드를 완성해야 합니다.

코드

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', 'abc', '가나다'];

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

  const addTags = (event) => {
    console.log(event)
    if (event.key === "Enter" && !tags.includes(event.target.value) && event.target.value !== "") {
      setTags([...tags, event.target.value])
      event.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">&#38;
                
              </span>
            </li>
          ))}
        </ul>
        <input
          className="tag-input"
          type="text"
          onKeyUp={(event) => {
            {
              addTags(event)
            }
          }}
          placeholder="Press enter to add tags"
        />
      </TagsInput>
    </>
  );
};
profile
내가 보려고 정리하는 블로그🔥

0개의 댓글