React

TodoList 만들기(2)

soheedev 2023. 4. 6. 22:55

앞서 만든 TodoList를 styled-components를 활용하여 꾸며보자.

테마 설정하기

컬러 팔레트를 하나 선택하여(https://colorhunt.co/) 해당 색상으로 palatte에 등록한다. (theme provider 사용)

 

/styles/theme.js

const palette = {
    yellow: '#F9F9C5',
    green: '#D9F8C4',
    orange: '#FAD9A1',
    red: '#F37878',
}

const common = {
    flexCenter: `
      display: flex;
      align-items: center;
      justify-content: center;
    `,
    flexAround: `
      display: flex;
      align-items: center;
      justify-content: space-around;
    `,
    flexColumnStart: `
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: flex-start;
    `,
}

const fontSizes = {
    title: '2rem',
    subtitle: '1.5rem',
    paragraph: '1rem',
}

const theme = {
    palette,
    common,
    fontSizes,
}

export default theme

 

스타일 작성하기

/TodoList/style.js

import styled from 'styled-components'

export const TodoContainer = styled.div`
  ${({ theme }) => theme.common.flexColumnStart};

  background-color: ${({ theme }) => theme.palette.red};
  border-radius: 10px;
  color: white;
  width: 50rem;
  height: 33rem;
  margin: 3rem auto;

  font-family: 'NotoSansBold';
`

export const TodoTitle = styled.p`
  font-size: ${({ theme }) => theme.fontSizes.subtitle};
`

/TodoList/index.jsx

import React, { useState, useRef } from 'react'
import TodoAdd from "@/components/TodoAdd"
import TodoItem from "@/components/TodoItem"
import * as S from './style'

function TodoList() {
  const todoId = useRef(2)
  const [todoData, setTodoData] = useState([
    { id: 1, date: '2022-07-28', content: '강의하기', checked: false },
  ])

  const todoRemoveHandler = (id) => {
    setTodoData(todoData.filter((itemData) => itemData.id !== id))
  }

  const todoCheckHandler = (id) => {
    setTodoData(
      todoData.map((itemData) =>
        itemData.id === id
          ? { ...itemData, checked: !itemData.checked }
          : itemData,
      ),
    )
  }

  return (
    <S.TodoContainer>
      <S.TodoTitle>나만의 Todo List</S.TodoTitle>
      <TodoAdd todoId={todoId} todoData={todoData} setTodoData={setTodoData} />
      {todoData.map((itemData) => {
        return (
          <TodoItem
            key={itemData.id}
            itemData={itemData}
            todoCheckHandler={todoCheckHandler}
            todoRemoveHandler={todoRemoveHandler}
          />
        )
      })}
    </S.TodoContainer>
  )
}

export default TodoList

 

/TodoAdd/style.js

import styled from 'styled-components'

export const AddContainer = styled.div`
  ${({ theme }) => theme.common.flexAround};
  flex-shrink: 0;

  color: black;
  width: 90%;
  margin-bottom: 1rem;

  font-family: 'NotoSansBold';
`

export const AddButton = styled.button`
  ${({ theme }) => theme.common.flexCenter};
  border: none;
  outline: none;
  cursor: pointer;

  background-color: ${({ theme }) => theme.palette.green};
  height: 2rem;
  width: 5rem;
  border-radius: 5px;

  &:hover {
    opacity: 0.5;
  }
`

export const AddInput = styled.input`
  border: none;
  outline: none;
  border-radius: 5px;
  padding: 0rem 0.5rem;

  height: 2rem;
  width: 30%;
`

/TodoAdd/index.jsx

import React, { useState } from 'react'
import * as S from './style'

function TodoAdd({ todoId, todoData, setTodoData }) {
  const [userInput, setUserInput] = useState({ date: '', content: '' })

  const userInputHandler = (e) => {
    const { name, value } = e.target
    setUserInput({ ...userInput, [name]: value })
  }

  const todoAddHandler = (userInput) => {
    setTodoData([
      ...todoData,
      {
        id: todoId.current,
        date: userInput.date,
        content: userInput.content,
        checked: false,
      },
    ])
    todoId.current += 1
  }

  return (
    <S.AddContainer>
      <S.AddInput type="date" name="date" onChange={userInputHandler} />
      <S.AddInput name="content" onChange={userInputHandler} />
      <S.AddButton onClick={() => todoAddHandler(userInput)}>
        추가하기
      </S.AddButton>
    </S.AddContainer>
  )
}

export default TodoAdd

 

/TodoItem/style.js

import styled from 'styled-components'

export const ItemContainer = styled.div`
  ${({ theme }) => theme.common.flexAround};
  flex-shrink: 0;

  background-color: ${({ theme }) => theme.palette.yellow};
  border-radius: 10px;
  color: black;
  width: 90%;
  height: 5rem;
  margin: 0.5rem;

  font-family: 'NotoSansBold';
`

export const ItemButton = styled.div`
  ${({ theme }) => theme.common.flexCenter};
  border: none;
  outline: none;
  cursor: pointer;

  &:hover {
    opacity: 0.5;
  }
`

export const ItemText = styled.div`
  font-size: ${({ theme }) => theme.fontSizes.paragraph};
  width: 30%;
`

체크박스와 제거하기 버튼을 아이콘을 사용하도록 하기 위해 react-icons를 설치한다.

(참고: https://react-icons.github.io/react-icons/search?q=checkbox)

yarn add react-icons

/TodoItem/index.jsx

import React from 'react'
import * as S from './style'
import { GrCheckbox, GrCheckboxSelected } from 'react-icons/gr'
import { AiOutlineCloseCircle } from 'react-icons/ai'

function TodoItem({ itemData, todoCheckHandler, todoRemoveHandler }) {
  const { id, date, content, checked } = itemData

  return (
    <S.ItemContainer>
      <S.ItemButton onClick={() => todoCheckHandler(id)}>
        {checked ? <GrCheckboxSelected /> : <GrCheckbox />}
      </S.ItemButton>
      <S.ItemText>{date}</S.ItemText>
      <S.ItemText>{content}</S.ItemText>
      <S.ItemButton onClick={() => todoRemoveHandler(id)}>
        <AiOutlineCloseCircle />
      </S.ItemButton>
    </S.ItemContainer>
  )
}

export default TodoItem

 

스타일을 입힌 TodoList

완료 여부 수정

완료 여부에 따라 TodoItem 컨포넌트의 opacity를 조절하도록 수정해본다.

 

/TodoItem/style.js

export const ItemContainer = styled.div`
  ${({ theme }) => theme.common.flexAround};
  flex-shrink: 0;

  background-color: ${({ theme }) => theme.palette.yellow};
  opacity: ${({ isChecked }) => (isChecked ? '0.5' : '1')}; /* 추가 */
  border-radius: 10px;
  color: black;
  width: 90%;
  height: 5rem;
  margin: 0.5rem;

  font-family: 'NotoSansBold';
`

/TodoItem/index.jsx

import React from 'react';
import * as S from './style';
import { GrCheckbox, GrCheckboxSelected } from 'react-icons/gr';
import { AiOutlineCloseCircle } from 'react-icons/ai';

function TodoItem({ itemData, todoCheckHandler, todoRemoveHandler }) {
  const { id, date, content, checked } = itemData;

  return (
    <S.ItemContainer isChecked={checked}>
      <S.ItemButton onClick={() => todoCheckHandler(id)}>
        {checked ? <GrCheckboxSelected /> : <GrCheckbox />}
      </S.ItemButton>
      <S.ItemText>{date}</S.ItemText>
      <S.ItemText>{content}</S.ItemText>
      <S.ItemButton onClick={() => todoRemoveHandler(id)}>
        <AiOutlineCloseCircle />
      </S.ItemButton>
    </S.ItemContainer>
  );
}

export default TodoItem;

 

완료한 Todo는 하단으로 배치(sort)

todoData가 변경될 때마다, 자동으로 데이터가 하단으로 소팅되도록 한다.

 

/TodoList/index.jsx (아래 내용 추가)

const [sortedData, setSortedData] = useState([])

useEffect(() => {
  setSortedData(todoData.sort((a, b) => a.checked - b.checked))
}, [todoData])

 

최종 완성된 TodoList

 

 

 

메가바이트스쿨 FE4기 수업자료를 참고하였습니다.

'React' 카테고리의 다른 글

TodoList 만들기(1)  (0) 2023.04.05
Styled-components  (0) 2023.03.21
useRef  (0) 2023.03.20
이벤트핸들러  (0) 2023.03.15
useEffect  (0) 2023.03.09