본문 바로가기
프로그래밍/개발 언어

React Custom Component

by monicada 2022. 7. 4.
728x90

modal.js

  • Modal 컴포넌트는 아래와 같은 state가 존재합니다. 필요에 따라서 state를 더 만들 수도 있습니다.
    • isOpen state는 모달 창의 열고 닫힘 여부를 확인할 수 있습니다.
  • ModalBtn 컴포넌트는 모달 창 제어를 위해 핸들러 함수 openModalHandler를 작성합니다.
    • openModalHandler 함수는
      • ModalBtn 클릭 시 발생되는 change 이벤트 핸들러입니다.
      • 클릭할 때마다 상태가 Boolean 값으로 변경됩니다.
  • ModalView 컴포넌트를 작성하고 isOpen 상태가 true 일 경우에만 렌더링합니다.
  • ModalBackdrop 컴포넌트를 작성하고 isOpen 상태가 true 일 경우에만 렌더링합니다.
import { useState } from 'react';
import styled from 'styled-components';

export const ModalContainer = styled.div`
// TODO : Modal을 구현하는데 전체적으로 필요한 CSS를 구현합니다.
`;

export const ModalBackdrop = styled.div`
 // TODO : Modal이 떴을 때의 배경을 깔아주는 CSS를 구현합니다.
`;

export const ModalBtn = styled.button`
  background-color: #4000c7;
  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를 구현합니다.
`;

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

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

  return (
    <>
      <ModalContainer onClick={OpenModalHandler}>
        <ModalBtn onClick={OpenModalHandler}>
          {isOpen ? "Opened!" : "Open Modal"}
        </ModalBtn>
        {isOpen ? <ModalBackdrop onClick={OpenModalHandler}>
        <ModalView> 
          <div onClick={OpenModalHandler}>
            &times;
          </div>
            Hello world!
          </ModalView>
        </ModalBackdrop>: ""}
      </ModalContainer>
    </>
  );
};

tap.js

  • Tab 컴포넌트는 아래와 같은 state가 존재합니다. 필요에 따라서 state를 더 만들 수도 있습니다.
    • currentTab state는 현재 tab의 index를 확인할 수 있습니다.
  • TabMenu 컴포넌트는 토글 버튼 제어를 위해 핸들러 함수 selectMenuHandler 를 가집니다.
    • selectMenuHandler 함수는
      • TabMenu 클릭 시 발생되는 change 이벤트 핸들러입니다.
      • 클릭할 때마다 상태가 index 값으로 변경됩니다.
  • li 요소를 이용해 메뉴를 생성하고, 각 메뉴를 눌렀을 때 뷰가 전환되도록 handler(selectMenuHandler) 함수를 작성합니다.
  • 조건부 스타일링과 currentTab 상태를 이용하여 클릭한 Tab 메뉴만 className(submenu focused)과 CSS 가 변경되도록 구현합니다. 조건부 스타일링은 앞서 토글에서 구현한 것과 같이 템플릿 리터럴과 삼항 연산자를 활용할 수 있습니다.
import { useState } from 'react';
import styled from 'styled-components';

// TODO: Styled-Component 라이브러리를 활용해 TabMenu 와 Desc 컴포넌트의 CSS를 구현합니다.

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-top: 0.8rem;
    padding-bottom: 0.8rem;
    padding-left: 0.9rem;
    text-align: center;
    padding-right: 0.9rem;
    cursor: pointer;
    border-radius: 10px;
  }

  .focused {
    ${'' /* 선택된 Tabmenu 에만 적용되는 CSS를 구현합니다.  */}
    color: #C0C0C0;
    background-color: #008080;
  }
  & div.desc {
    text-align: center;
  }
`;

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

export const Tab = () => {
  // TIP: Tab Menu 중 현재 어떤 Tab이 선택되어 있는지 확인하기 위한
  // currentTab 상태와 currentTab을 갱신하는 함수가 존재해야 하고, 초기값은 0 입니다.
  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) => {
    // TIP: parameter로 현재 선택한 인덱스 값을 전달해야 하며, 이벤트 객체(event)는 쓰지 않습니다
    // TODO : 해당 함수가 실행되면 현재 선택된 Tab Menu 가 갱신되도록 함수를 완성하세요.
    setCurrentTab(index);
  };

  return (
    <>
      <div>
        <TabMenu>
          {/*TODO: 아래 하드코딩된 내용 대신에, map을 이용한 반복으로 코드를 수정합니다.*/}
          {/*TIP: li 엘리먼트의 class명의 경우 선택된 tab 은 'submenu focused' 가 되며, 
                  나머지 2개의 tab은 'submenu' 가 됩니다.*/}
          {/* <li className="submenu">{menuArr[0].name}</li>
          <li className="submenu">{menuArr[1].name}</li>
          <li className="submenu">{menuArr[2].name}</li> */}
          {menuArr.map((menu, idx) => {
            return (
              <li 
              key={idx}
              className={idx === currentTab ? 'submenu focused' : 'submenu' } 
              onClick={() => selectMenuHandler(idx)}>
              {menu.name}
              </li>
            );
          })}
        </TabMenu>
        <Desc>
          {/*TODO: 아래 하드코딩된 내용 대신에, 현재 선택된 메뉴 따른 content를 표시하세요*/}
          <p>{menuArr[currentTab].content}</p>
        </Desc>
      </div>
    </>
  );
};

tag.js

  • Tag 컴포넌트는 아래와 같은 state가 존재합니다. 필요에 따라서 state를 더 만들 수도 있습니다.
    • tags state는 배열의 형태입니다.
    • 초기값으로 initialTags 를 가지고 있습니다.
  • TagsInput 컴포넌트는 핸들러 함수 addTags를 가집니다.
    • addTags 함수는
      • input 창에 Enter 키를 누를 때 발생되는 change 이벤트 핸들러입니다.
      • Enter를 입력할 때마다 입력한 값이 state에 추가됩니다.
  • span.tag-close-icon 에는 아직 실제 작동하는 삭제 아이콘(x)이 없습니다. 삭제 아이콘을 만들고, 이 버튼이 동작하도록 handler (removeTags ) 함수를 작성합니다.
import { useState } from 'react';
import styled from 'styled-components';

// TODO: Styled-Component 라이브러리를 활용해 여러분만의 tag 를 자유롭게 꾸며 보세요!

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: #4000c7;
        > .tag-close-icon {
        display: block;
        width: 16px;
        height: 16px;
        line-height: 16px;
        text-align: center;
        font-size: 14px;
        margin-left: 8px;
        color: #4000c7;
        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 #4000c7;
  }

`;

export const Tag = () => {
  const initialTags = ['CodeStates', 'kimcoding'];

  const [tags, setTags] = useState(initialTags);
  const removeTags = (indexToRemove) => {
    // TODO : 태그를 삭제하는 메소드를 완성하세요.
    setTags(tags.filter(tag => indexToRemove !== tags.indexOf(tag)));
  };
  
  const addTags = (event) => {
    // TODO : tags 배열에 새로운 태그를 추가하는 메소드를 완성하세요. 
    // 이 메소드는 태그 추가 외에도 아래 3 가지 기능을 수행할 수 있어야 합니다.
    // - 이미 입력되어 있는 태그인지 검사하여 이미 있는 태그라면 추가하지 말기
    // - 아무것도 입력하지 않은 채 Enter 키 입력시 메소드 실행하지 말기
    // - 태그가 추가되면 input 창 비우기
    if (!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 className='tag-close-icon'onClick={() => removeTags(index)}>&times; 
                {/* TODO :  tag-close-icon이 tag-title 오른쪽에 x 로 표시되도록 하고,
                            삭제 아이콘을 click 했을 때 removeTags 메소드가 실행되어야 합니다. */}
              </span>
            </li>
          ))}
        </ul>
        <input
          className='tag-input'
          type='text'
          onKeyUp={(event) => event.key === 'Enter' ? addTags(event) : ''}
          placeholder='Press enter to add tags'
        />
      </TagsInput>
    </>
  );
};

toggle.js

  • Toggle 컴포넌트는 아래와 같은 state가 존재합니다. 필요에 따라서 state를 더 만들 수도 있습니다.
    • isOn state는 토글 버튼의 on/off 여부를 확인할 수 있습니다.
  • ToggleContainer 컴포넌트는 토글 버튼 제어를 위해 핸들러 함수 toggleHandler를 작성합니다.
    • toggleHandler 함수는
      • ToggleContainer 클릭 시 발생되는 change 이벤트 핸들러입니다.
      • 클릭할 때마다 상태가 Boolean 값으로 변경됩니다.
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: 100px;
    height: 24px;
    border-radius: 30px;
    background-color: #FF0000;
    // TODO : .toggle--checked 클래스가 활성화 되었을 경우의 CSS를 구현합니다.
  }

  > .toggle-circle {
    position: absolute;
    top: 1px;
    left: 1px;
    width: 22px;
    height: 22px;
    border-radius: 50%;
    background-color: #ffffff;
    // TODO : .toggle--checked 클래스가 활성화 되었을 경우의 CSS를 구현합니다.
  }
`;

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

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

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


  return (
    <>
      <ToggleContainer 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>
      {/* TODO : Desc 컴포넌트를 활용해야 합니다. */}
      <Desc>
        {isOn ? "Toggle Switch ON" : "Toggle Switch OFF"}
      </Desc>
      {/* TIP:  Toggle Switch가 ON인 상태일 경우에 Desc 컴포넌트 내부의 텍스트를 'Toggle Switch ON'으로, 그렇지 않은 경우 'Toggle Switch OFF'가 됩니다. 조건부 렌더링을 활용하세요. */}
    </>
  );
};

'프로그래밍 > 개발 언어' 카테고리의 다른 글

[React] Cmarket Redux  (0) 2022.07.06
Redux  (0) 2022.07.06
[React] Custom Component  (0) 2022.07.04
JSON.stringify  (0) 2022.06.24
프로토타입과 클래스  (0) 2022.05.25

댓글