React

[React] 투두리스트 웹 만들기

Puft 2024. 1. 16. 17:59

안녕하세요 오랜만에 돌아왔습니다.

 

방학한지 3주정도 지났는데 이제야 공부 시작하네요.

 

소프트웨어 공학이라는 전공에서 팀 프로젝트로 투두리스트 어플을 만들었었는데

 

저는 디자인 담당이라 기능 구현을 하지 못했습니다.

 

이번 기회에 혼자서 따라가면서 만들어 보려고 합니다.

 

이번 프로젝트는 벨로퍼트 님의 게시물을 참고하였습니다.

 

벨로퍼트와 함께하는 모던 리액트 · GitBook (vlpt.us)

 

벨로퍼트와 함께하는 모던 리액트 · GitBook

벨로퍼트와 함께하는 모던 리액트 본 강의자료는 패스트캠퍼스 온라인 강의에서 제공하는 리액트 강의에서 사용되는 강의 문서입니다. 이 튜토리얼은 여러분들이 JavaScript 의 기초를 잘 알고있

react.vlpt.us

 

먼저 프로젝트를 하나 만들어 줄게요.

 

터미널에 npx create-react-app todo를 입력하여 만들어 주었습니다.

 

npm install react-icons --save

 

npm install --save styled-components

 

react-icons와 styled-components 라이브러리를 설치해 줍니다.

 

npm run start로 잘 구동되는지 확인해주고

 

import React from 'react';
import { createGlobalStyle } from 'styled-components';

const GlobalStyle = createGlobalStyle`
  body {
    background: #e9ecef;
  }
`;

function App() {
  return (
    <>
      <GlobalStyle />
      <div>안녕하세요</div>
    </>
  );
}

export default App;

 

#e9ecef라는 색상을 이용해서 배경색을 설정해 줍니다.

 

새롭게 알게 된 것이 createGlobalStyle인데

 

styled-components에 있는 함수로, 글로벌 스타일을 적용해 준다고 합니다.

 

적용된 모습

 

그 다음 src 경로 안에 컴포넌트 들을 저장할 Components 폴더를 하나 만들어 줍니다.

 

그리고 가장 먼저 만들어야 할 것은 템플릿 컴포넌트입니다.

 

TodoTemplate.js라는 파일을 하나 만들어 줍니다.

 

components/TodoTemplate.js

import React from 'react';
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;
`;

function TodoTemplate({ children }) {
  return <TodoTemplateBlock>{children}</TodoTemplateBlock>;
}

export default TodoTemplate;

 

position : relative   -  원래 위치 기준으로 상대적으로 배치

 

border-radius   -  테두리 둥글게 만들어줌

 

box - shadow   -  박스에 그림자를 만들어줌

 

display : flex  -  기본 row (왼쪽 -> 오른쪽)

 

flex-direction : column  -  위에서 아래로 정렬

 

이제 app.js에서 적용시켜 봅니다.

 

import TodoTemplate from './Components/TodoTemplate';

 

import를 해주고 TodoTemplate 태그로 감싸주면 되겠네요.

 

박스 생성

 

투두리스트가 보여질 하얀색 박스를 생성했습니다.

 

다음은 app.head라는 머리글에 올 컴포넌트를 만들어 보겠습니다.

 

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;
  }
`;

function TodoHead() {
  return (
    <TodoHeadBlock>
      <h1>2019년 7월 10일</h1>
      <div className="day">수요일</div>
      <div className="tasks-left">할 일 2개 남음</div>
    </TodoHeadBlock>
  );
}

export default TodoHead;

 

padding - 방향 : 간격 생성

 

border - bottom : 1px solid : 박스 라인 아래쪽 생성

 

새롭게 알게 된 css  -  font-weight : bold 는 글자를 굵게 해주는 css라네요.

 

(normal로 해주면 보통 굵기)

 

Todo Header

 

잘 적용된 모습입니다.

 

이번에는 할 일 리스트를 보여주는 TodoList 컴포넌트를 만들어 줍시다.

 

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

const TodoListBlock = styled.div`
  flex: 1;
  padding: 20px 32px;
  padding-bottom: 48px;
  overflow-y: auto;
  background: gray; /* 사이즈 조정이 잘 되고 있는지 확인하기 위한 임시 스타일 */
`;

function TodoList() {
  return <TodoListBlock>TodoList</TodoListBlock>;
}

export default TodoList;

 

새로 알게 된 내용인 overflow-y는 세로 축으로 내용이 넘칠 때 어떻게 처리할 것인지 도와주는 함수입니다.

 

가로 축은 overflow-x라고 합니다.

 

overflow-y의 속성 ▽

 

visible (기본값) : 특정 요소가 박스를 넘어 가더라도, 그대로 보여준다.

 

hidden : 부모요소의 범위를 넘어가는 자식 요소의 부분은 보이지 않도록 처리한다.

         (세로 스크롤바가 나타나지 않을 뿐 브라우저에 따라 가로 스크롤바는 나타남)

 

scroll : 부모요소의 범위를 넘어가는 자식요소의 부분은 보이지 않지만, 사용자가 확인 할 수 있도록 스크롤바를 표시한다.

         (세로 스크롤바 항상 표시)

 

auto : 부모요소의 범위를 넘어가는 자식요소의 부분이 있을 경우 해당 부분을 보이지 않도록 처리하고, 사용자가 해당 부분을 확인 할 수 있도록 스크롤바를 표시 한다.

         (내용이 넘칠때만 세로 스크롤바 표시)

 

보통은 auto를 사용하는 것이 적절해 보입니다.

 

flex : 1도 저에게는 생소한데 화면의 크기에 따라 유연하게 조정해주는 속성이라고 합니다.

 

app.js에 적용해 주면 될 것 같습니다.

 

import React from 'react';
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;

 

템플릿 태그 안에 <TodoHead> 와 <TodoList>를 넣어주면 됩니다.

 

TodoList

 

TodoList가 보여질 부분이 그레이 색상으로 보입니다.

 

적용된 것이 확인 되었기 때문에 그레이 색상은 없애줍니다.

 

다음은 TodoList 내용이 보여질 TodoItem을 만들어 줍니다.

 

import React from 'react';
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;
    }
  }
`;

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;
    `}
`;

function TodoItem({ id, done, text }) {
  return (
    <TodoItemBlock>
      <CheckCircle done={done}>{done && <MdDone />}</CheckCircle>
      <Text done={done}>{text}</Text>
      <Remove>
        <MdDelete />
      </Remove>
    </TodoItemBlock>
  );
}

export default TodoItem;

 

여기서 코드를 이해하는데 시간이 좀 걸렸습니다.

 

const TodoItemBlock = styled.div`
  display: flex;
  align-items: center;
  padding-top: 12px;
  padding-bottom: 12px;
  &:hover {
    ${Remove} {
      display: initial;
    }
  }
`;

 

이 부분이 ComponentSelector라고 해서 TodoItemBlock 위에 커서가 있으면 Remove 컴포넌트를 보여주라는 의미 입니다.

 

${props =>
    props.done &&
    css`
      border: 1px solid #38d9a9;
      color: #38d9a9;
    `}

 

&&연산자로 props로 받은 상태가 done이면 css부분을 보여주라는 의미입니다.

 

TodoItem

 

할 일 요소들에 커서를 갖다되면 쓰레기통 모양의 Remove 컴포넌트를 보여주는 모습입니다.

 

다음은 새로운 할 일을 등록할 수 있는 TodoCreate를 만들어 보겠습니다.

 

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;
`;

function 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;

 

주의해야 할 것은 ComponentSelector에는 hover과 active 그리고 focus 속성이 있는데

 

hover : 마우스가 해당 요소 위에 있을 때 함수를 실행

 

active : 마우스로 해당 요소를 클릭하는 순간부터 때는 순간 까지 함수를 실행

 

focus : 마우스로 해당 요소를 클리하면 함수를 실행

 

UI면에서 유용한 ComponentSelector를 알게 되어서 좋았던 것 같습니다.

 

기본 화면
마우스를 갖다 댔을 때

 

+ 버튼 클릭했을 때

 

x 버튼에 마우스를 갖다 댔을 때

 

사용자 측에서 전부 다 동적으로 바뀌는 UI를 만들 수 있다는 점에서 ComponentSelector가 강점이 있는 것 같습니다.

 

여기까지가 UI 부분이었고

 

다음 시간에는 기능 구현을 해보도록 하겠습니다.

 

감사합니다!