우선 DB 연동 결과를 확인할 수 있을 정도로만 작업할 것이다.
DB와 직접적으로 관련된 부분인 추가, 삭제, 수정 ...
즉, API 연동과 함께 진행해야 하는 부분은 DB연동 완료 후 진행할 계획이다.
이 파트에서 집중해야 할 부분은 Context와 하위 컴포넌트에서 사용되는 useContext라고 생각한다. 왜냐하면 내가 미숙했던 부분이니까 ㅎㅎ
styled-components를 이용한 스타일링을 통해 기본적인 TodoCard의 틀을 그려보자.
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;
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;
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;
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;
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;
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;