[React] 기본 작업

박은지·2022년 3월 1일
0

TodoCard

목록 보기
3/6

1. 기본 작업

우선 DB 연동 결과를 확인할 수 있을 정도로만 작업할 것이다.
DB와 직접적으로 관련된 부분인 추가, 삭제, 수정 ...
즉, API 연동과 함께 진행해야 하는 부분은 DB연동 완료 후 진행할 계획이다.

이 파트에서 집중해야 할 부분은 Context와 하위 컴포넌트에서 사용되는 useContext라고 생각한다. 왜냐하면 내가 미숙했던 부분이니까 ㅎㅎ
styled-components를 이용한 스타일링을 통해 기본적인 TodoCard의 틀을 그려보자.


App.js

import React, { useState } from 'react';
import './App.css';
import styled, {createGlobalStyle} from "styled-components";
import { FaChevronLeft, FaChevronRight } from 'react-icons/fa';

import TodoTemplate from './components/TodoTemplate';
import TodoHead from './components/TodoHead';
import TodoList from './components/TodoList';
import TodoForm from './components/TodoForm';

// styled-components
const GlobalStyle = createGlobalStyle`
  body {
    background: #e9ecef;
  }
`;
const Carousel = styled.div`
  width: 1656px;
  margin: 0 auto;

  display: flex;
  // overflow: hidden;
  justify-content: space-around;  // 카드 사이에 균등한 여백을 두고 정렬

  border: 4px solid red;
  // width:500px;
  // height: 1000px;
  // background: pink;
`;
const Slider = styled.div`
  width: 2760px; 
  display: flex;
  
  // position: relative;
  // height: 900px;
  // margin: auto 0;
  // background: powderblue;
`;

// Context API
export const TodoContext = React.createContext();

// APP COMPONEMT
function App() {

  const [n, setN] = useState(0);

  // onPrev
  const onPrev = () => {
    console.log('PREV : ', n-1);
    setN(n - 1);
  }

  // onNext
  const onNext = () => {
    console.log('NEXT : ', n+1);
    setN(n + 1);
  }

  return (
    <>
    <GlobalStyle />

    <div style={{display: 'flex'}}>
      <FaChevronLeft className='controlBtn prev' onClick={onPrev} />

      <Carousel className='carousel'>
        <Slider className='slider'>
          
          <TodoContext.Provider value={{n}} >
            <TodoTemplate>
              <TodoHead />
              <TodoList />
              <TodoForm />
            </TodoTemplate>
          </TodoContext.Provider>

        </Slider>
      </Carousel>
      {console.log('N : ',n)}

      <FaChevronRight className='controlBtn next' onClick={onNext} />
    </div>
    </>
  );
}

export default App;

TodoTemplate.jsx

import React, { useContext } from "react";
import styled from 'styled-components';
import { TodoContext } from "../App";

// styled-components
const TodoTemplateBlock = styled.div `
  width: 512px;
  min-width: 512px;
  height: 768px;
  margin: 70px 20px 60px ;

  position: relative;
  background: #fff;
  border-radius: 16px;
  box-shadow: 0 0 8px rgba(0, 0, 0, 0.04);

  display: flex;
  flex-direction: column;
`;

function TodoTemplate({children}) {

  const {n} = useContext(TodoContext);

  return (
    <TodoTemplateBlock>
      {children}

      {/* ----- 디버깅 ----- */}
      <h2 style={{color:'red'}}>{n}</h2>
    </TodoTemplateBlock>
  );
}

export default TodoTemplate;

TodoHead.jsx

import React, { useContext } from "react";
import styled from "styled-components";
import { TodoContext } from "../App";

// styled-components
const TodoHeadBlock = styled.div`
  padding: 48px 32px 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;
  }
`;

// TODOHEAD COMPONENT
function TodoHead() {

  const {n} = useContext(TodoContext);

  // 날짜, 요일
  const now = new Date();
  const today = new Date(now.setDate(now.getDate() + n));
  const dateString = today.toLocaleDateString('ko-KR', {
    year: 'numeric',
    month: 'long',
    day: 'numeric',
  });
  const dayName = today.toLocaleDateString('ko-KR', {
    weekday: 'long',
  });

  return (
    <>
      <TodoHeadBlock>
        <h1>{dateString}</h1>
        <div className='day'>{dayName}</div>
        {/* <div className='tasks-left'>할 일 {undoneTasks.length}개 남음</div> */}
        <div className='tasks-left'>할 일 0개 남음</div>

        {/* ----- 디버깅 ----- */}
        {/* <p>TodoHead  {n}</p> */}

      </TodoHeadBlock>
    </>
  );
}

export default TodoHead;

TodoList.jsx

import React, { useContext } from "react";
import styled from "styled-components";
import TodoItem from "./TodoItem";
import { TodoContext } from "../App";

// styled-components
const TodoListBlock = styled.div`
  flex: 1;
  padding: 20px 32px 48px;
  overflow-y: auto;
`;


// TODOLIST COMPONENT
function TodoList() {

  const {n} = useContext(TodoContext);

  return (
    <>
      <TodoListBlock>
        <TodoItem />
    
        {/* -------- 디버깅 --------*/}
        <p>TodoList {n}</p>
      </TodoListBlock>

    </>
  );
}

export default TodoList;

TodoItem.jsx

import React, { useContext } from "react";
import styled, {css} from "styled-components";
import { MdDone, MdDelete, MdAdd } from "react-icons/md";
import { TodoContext } from "../App";

// styled-components
const Remove = styled.div`
  display: flex;
  align-items: center;
  justify-content: center;
  color: #dee2e6;
  font-size: 24px;
  cursor: pointer;
  opacity: 0;
  &:hover {
    color: #ff6b6b;
  }
`;
const TodoItemBlock = styled.div`
  display: flex;
  align-items: center;
  padding-top: 12px;
  padding-bottom: 12px;
  &:hover {
    ${Remove} {
      opacity: 1;
    }
  }
`;
const CheckCircle = styled.div`
  width: 32px;
  height: 32px;
  border-radius: 20px;
  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;
  &:hover {
    cursor: pointer;
  }
  ${props => props.done && css`
    color: #ced4da;
  `}
`;

// TODOITEM COMPONENT
function TodoItem() {

  const {n} = useContext(TodoContext);

  // onDone
  const onDone = () => {
    console.log('Done / Undone')
  }

  // onEdit
  const onEdit = () => {
    console.log('Edit');
  }

  // onRemove
  const onRemove = () => {
    console.log('Remove');
  }


  return (
    <>
      <TodoItemBlock>
        <CheckCircle onClick={onDone}>{<MdDone />}</CheckCircle>
        <Text onClick={onEdit}>React 프로젝트</Text>
        <Remove onClick={onRemove}>
          <MdDelete />
        </Remove>
      </TodoItemBlock>
      {/* -------- 디버깅 --------*/}
      <p>TodoItem {n}</p>
    </>
  );
}

export default TodoItem;

TodoForm.jsx

import React, { useState, useContext, useRef } from "react";
import styled, {css} from "styled-components";
import { MdAdd } from "react-icons/md";
import { TodoContext, PageContext } from "../App";

const CircleButton = styled.button`
  background: #38d9a9;
  &:hover {
    background: #63e6be;
  }
  &:active {
    background: #20c997;
  }

  z-index: 5;
  cursor: pointer;
  width: 80px;
  height: 80px;
  dusplay: flex;
  align-items: center;
  justify-content: center;

  position: absolute;
  left: 50%;
  bottom 0px;
  transform: translate(-50%, 50%);

  font-size: 60px;
  color: #fff;
  border-radius: 40px;
  
  border: none;
  outline: none;

  // circel버튼을 누르면 색이 변하면서 45도 회전하는 효과
  transition: 0.125s all ease-in;
  ${props => props.open && css`
    background: #ff6b6b;
    &:hover {
      background: #ff8787;
    }
    &:active {
      background: #fa5252;
    }
    transform: translate(-50%, 50%) rotate(45deg);
  `}
`;
// circle 버튼을 누르면 입력폼 나타남
const InsertFormPositioner = styled.div`
  width: 100%;
  bottom: 0;
  left: 0;
  position: absolute;
`;
// form 태그X
// const InsertForm = styled.div` 
// form 태그O
const InsertForm = styled.form` 
  background: #f8f9fa;
  padding: 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;
  &::placeholder {
    color: #999;
  }
`;
const Textarea = styled.textarea`
  height: 400px;
  padding: 12px;
  margin-top: 16px;
  border-radius: 4px;
  border: 1px solid #dee2e6;
  width: 100%;
  outline: none;
  font-size: 18px;
  box-sizing: border-box;
  resize: none;
  &::placeholder {
    font-weight: 600;
    color: #999;
  }
`
const SaveBtn = styled.div`
  width: 100%;
  height: 40px;
  margin-top: 16px;
  padding-top: 8px;
  box-sizing: border-box;
  background: #6666ff;
  color: #fff;
  font-size: 18px;
  font-weight: 500;
  letter-spacing: ;
  text-align: center;
  border-radius: 8px;
  &:hover {
    cursor: pointer;
    background: #8080ff;
  }
`

function TodoForm() {
  
  const [open, setOpen] = useState(false);
  const onFormToggle = () => setOpen(!open);

  const onSubmit = (e) => {
    console.log('Save');
  }

  return (
    <>
      {(open) && (
        <InsertFormPositioner>
          <InsertForm>
            <Input
              placeholder="Title" 
              autoFocus
              ref={titleRef}
            />
            <Textarea
              id='nextFocus'
              placeholder="Contents" 
              ref={contentsRef}
            />
            <SaveBtn onClick={onSubmit}>Save</SaveBtn>
          </InsertForm>
        </InsertFormPositioner>
      )}
      <CircleButton onClick={onFormToggle} open={open} editOn={editOn}>
       <MdAdd style={{width: '70px', height: '70px', position: 'relative', right:'1px', top: '4px'}} />
      </CircleButton>
      {/* --------- 디버깅 ---------- */}
      {/* <p>TodoForm</p> */}
    </>
  );
}

export default TodoForm;

0개의 댓글