ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [React] Todolist 만들기 정리(1)
    📌 React 2022. 9. 28. 00:04

     

    벨로퍼트님의 https://react.vlpt.us/mashup-todolist/01-create-components.html 보고 직접 구현했던 과정들을 정리하려고 한다.

    cra설정

     yarn create react-app react-todolist

    이번 프로젝트에서 필요한 라이브러리 react-icons 와 styled-components 를 설치.

     

    $ cd react-todolist
    $ yarn add react-icons styled-components

     

     

    컴포넌트 정리

    TodoProvider

    • useReducer 를 사용하여 상태를 관리하는 컴포넌트이다.

    TodoTemplate

    • 투두리스트의 레이아웃을 설정하는 컴포넌트이다.
    • 페이지 중앙에 그림자가 적용된 흰색 박스를 보여준다.

    TodoHead

    • 오늘의 날짜와 요일을 보여주고, 앞으로 해야 할 일이 몇 개 남았는지 보여준다.

    TodoList

    • 할 일에 대한 정보가 들어있는 todos 배열을 내장 함수 map을 사용하여 여러 개의 TodoItem 컴포넌트를 렌더링 해준다.

    TodoItem

    • 각 할 일에 대한 정보를 렌더링 해주는 컴포넌트이다.
    • 좌측에 있는 원을 누르면 할 일의 완료 여부를 toggle 할 수 있다.
    • 할 일이 완료됐을 땐 좌측에 체크가 나타나고 텍스트의 색상이 연해진다.
    • 마우스를 올리면 휴지통 아이콘이 나타나고 이를 누르면 항목이 삭제된다.

    TodoCreate

    • 새로운 할 일을 등록할 수 있게 해주는 컴포넌트이다.
    • Todo Template의 하단부에 초록색 원 버튼을 렌더링 해주고, 이를 클릭하면 할 일을 입력할 수 있는 폼이 나타난다.
    • 버튼을 다시 누르면 폼이 사라진다.

     


     

    App.js

    import React from "react";
    import { createGlobalStyle } from "styled-components";
    import TodoTemplate from "./components/TodoTemplate";
    
    const GlobalStyle = createGlobalStyle`
      body {
        background: #e9ecef;
      }
    `;
    
    function App() {
      return (
        <>
          <GlobalStyle />
          <TodoTemplate>안녕하세요</TodoTemplate>
        </>
      );
    }
    
    export default App;

     

    src / components / TodoTemplate.js

    import styled from "styled-components";
    
    const TodoTemplateBlock = styled.div`
      width: 512px;
      height: 768px;
    
      position: relative;
      background: white;
      border-radius: 16px;
      box-shadow: 0 0 8px 0 rgba(0, 0, 0, 0.04);
    
      margin: 0 auto;
    
      margin-top: 96px;
      margin-bottom: 32px;
      display: flex;
      flex-direction: column;
    `;
    
    const TodoTemplate = ({ children }) => {
      return <TodoTemplateBlock>{children}</TodoTemplateBlock>;
    };
    
    export default TodoTemplate;

     

    위 두개의 결과 화면

     

     


     

    App.js

    import React from "react";
    import { createGlobalStyle } from "styled-components";
    import TodoTemplate from "./components/TodoTemplate";
    import TodoHead from "./components/TodoHead";
    
    const GlobalStyle = createGlobalStyle`
      body {
        background: #e9ecef;
      }
    `;
    
    function App() {
      return (
        <>
          <GlobalStyle />
          <TodoTemplate>
            <TodoHead />
          </TodoTemplate>
        </>
      );
    }
    
    export default App;

     

    src/components/TodoHead.js

    import React from "react";
    import styled from "styled-components";
    
    const TodoHeadBlock = styled.div`
      padding-top: 48px;
      padding-left: 32px;
      padding-right: 32px;
      padding-bottom: 24px;
      border-bottom: 1px solid #e9ecef;
      h1 {
        margin: 0;
        font-size: 36px;
        color: #343a40;
      }
      .day {
        margin-top: 4px;
        color: #868e96;
        font-size: 21px;
      }
      .tasks-left {
        color: #20c997;
        font-size: 18px;
        margin-top: 40px;
        font-weight: bold;
      }
    `;
    
    const TodoHead = () => {
      return (
        <TodoHeadBlock>
          <h1>2022년 10월 01일</h1>
          <div className="day">토요일</div>
          <div className="tasks-left">할 일 3개 남음</div>
        </TodoHeadBlock>
      );
    };
    
    export default TodoHead;

     

     

     


     

    App.js

    import { createGlobalStyle } from "styled-components";
    import TodoTemplate from "./components/TodoTemplate";
    import TodoHead from "./components/TodoHead";
    import TodoList from "./components/TodoList";
    
    const GlobalStyle = createGlobalStyle`
      body {
        background: #e9ecef;
      }
    `;
    
    function App() {
      return (
        <>
          <GlobalStyle />
          <TodoTemplate>
            <TodoHead />
            <TodoList />
          </TodoTemplate>
        </>
      );
    }
    
    export default App;

     

    src/components/TodoList.js

    import styled from "styled-components";
    
    const TodoListBlock = styled.div`
      flex: 1;
      padding: 20px 32px;
      padding-bottom: 48px;
      overflow-y: auto;
      background: gray; /* 사이즈 조정이 잘 되고 있는지 확인하기 위한 임시 스타일 */
    `;
    
    const TodoList = () => {
      return <TodoListBlock>TodoList</TodoListBlock>;
    };
    
    export default TodoList;

     

     

     

     


     

     

    src/components/TodoItem.js

    이 컴포넌트에서는 react-icons 에서 MdDone 과 MdDelete 아이콘을 사용.

    import styled, { css } from "styled-components";
    import { MdDone, MdDelete } from "react-icons/md";
    
    const Remove = styled.div`
      display: flex;
      align-items: center;
      justify-content: center;
      color: #dee2e6;
      font-size: 24px;
      cursor: pointer;
      &:hover {
        color: #ff6b6b;
      }
      display: none;
    `;
    
    const TodoItemBlock = styled.div`
      display: flex;
      align-items: center;
      padding-top: 12px;
      padding-bottom: 12px;
      &:hover {
        ${Remove} {
          display: initial;
        }
      }
      /* 
      여기서 사용된 기능은 Component Selector 라는 기능입니다. 
      이 스타일은 TodoItemBlock 위에 커서가 있을 때, 
      Remove 컴포넌트를 보여주라는 의미를 가지고 있습니다.
       */
    `;
    
    const CheckCircle = styled.div`
      width: 32px;
      height: 32px;
      border-radius: 16px;
      border: 1px solid #ced4da;
      font-size: 24px;
      display: flex;
      align-items: center;
      justify-content: center;
      margin-right: 20px;
      cursor: pointer;
      ${(props) =>
        props.done &&
        css`
          border: 1px solid #38d9a9;
          color: #38d9a9;
        `}
    `;
    
    const Text = styled.div`
      flex: 1;
      font-size: 21px;
      color: #495057;
      ${(props) =>
        props.done &&
        css`
          color: #ced4da;
        `}
    `;
    
    const TodoItem = ({ id, done, text }) => {
      return (
        <TodoItemBlock>
          <CheckCircle done={done}>{done && <MdDone />}</CheckCircle>
          <Text done={done}>{text}</Text>
          <Remove>
            <MdDelete />
          </Remove>
        </TodoItemBlock>
      );
    };
    
    export default TodoItem;

     

    src/components/TodoList.js

    import styled from "styled-components";
    import TodoItem from "./TodoItem";
    
    const TodoListBlock = styled.div`
      flex: 1;
      padding: 20px 32px;
      padding-bottom: 48px;
      overflow-y: auto;
    `;
    
    const TodoList = () => {
      return (
        <TodoListBlock>
          <TodoItem text="프로젝트 생성하기" done={true} />
          <TodoItem text="컴포넌트 스타일링 하기" done={true} />
          <TodoItem text="Context 만들기" done={false} />
          <TodoItem text="기능 구현하기" done={false} />
        </TodoListBlock>
      );
    };
    
    export default TodoList;

     

     


    src/components/TodoCreate.js

    이 컴포넌트에서는 useState 를 사용하여 토글 할 수 있는 open 값을 관리한다.

    이 값이 true 일 때에는 아이콘을 45도 돌려서 X 모양이 보여지게 한 후, 버튼 색상을 빨간색으로 바꿔준다.

    그리고, 할 일을 입력 할 수 있는 폼도 보여준다

    import React, { useState } from "react";
    import styled, { css } from "styled-components";
    import { MdAdd } from "react-icons/md";
    
    const CircleButton = styled.button`
      background: #38d9a9;
      &:hover {
        background: #63e6be;
      }
      &:active {
        background: #20c997;
      }
    
      z-index: 5;
      cursor: pointer;
      width: 80px;
      height: 80px;
      display: block;
      align-items: center;
      justify-content: center;
      font-size: 60px;
      position: absolute;
      left: 50%;
      bottom: 0px;
      transform: translate(-50%, 50%);
      color: white;
      border-radius: 50%;
      border: none;
      outline: none;
      display: flex;
      align-items: center;
      justify-content: center;
    
      transition: 0.125s all ease-in;
      ${(props) =>
        props.open &&
        css`
          background: #ff6b6b;
          &:hover {
            background: #ff8787;
          }
          &:active {
            background: #fa5252;
          }
          transform: translate(-50%, 50%) rotate(45deg);
        `}
    `;
    
    const InsertFormPositioner = styled.div`
      width: 100%;
      bottom: 0;
      left: 0;
      position: absolute;
    `;
    
    const InsertForm = styled.form`
      background: #f8f9fa;
      padding-left: 32px;
      padding-top: 32px;
      padding-right: 32px;
      padding-bottom: 72px;
    
      border-bottom-left-radius: 16px;
      border-bottom-right-radius: 16px;
      border-top: 1px solid #e9ecef;
    `;
    
    const Input = styled.input`
      padding: 12px;
      border-radius: 4px;
      border: 1px solid #dee2e6;
      width: 100%;
      outline: none;
      font-size: 18px;
      box-sizing: border-box;
    `;
    
    const TodoCreate = () => {
      const [open, setOpen] = useState(false);
    
      const onToggle = () => setOpen(!open);
    
      return (
        <>
          {open && (
            <InsertFormPositioner>
              <InsertForm>
                <Input autoFocus placeholder="할 일을 입력 후, Enter 를 누르세요" />
              </InsertForm>
            </InsertFormPositioner>
          )}
          <CircleButton onClick={onToggle} open={open}>
            <MdAdd />
          </CircleButton>
        </>
      );
    };
    
    export default TodoCreate;

     

    App.js

    import { createGlobalStyle } from "styled-components";
    import TodoTemplate from "./components/TodoTemplate";
    import TodoHead from "./components/TodoHead";
    import TodoList from "./components/TodoList";
    import TodoCreate from "./components/TodoCreate";
    
    const GlobalStyle = createGlobalStyle`
      body {
        background: #e9ecef;
      }
    `;
    
    function App() {
      return (
        <>
          <GlobalStyle />
          <TodoTemplate>
            <TodoHead />
            <TodoList />
            <TodoCreate />
          </TodoTemplate>
        </>
      );
    }
    
    export default App;

     

     

     

     

    UI 구현은 여기까지!

     

     

     
    반응형

    댓글

Designed by Tistory & Awesome Soo © 2022 All rights reserved.