Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,23 @@
# react-todo-list-precourse
# react-todo-list-precourse

## 기능 정리

### 필수 기능

- [참고](https://todomvc.com/examples/react/dist/)
- [x] 할 일 목록 뷰
- [x] 할 일 추가
- input에 입력한 후 Enter나 추가 버튼을 이용해 목록에 추가
- 아무것도 입력되 있지 않은 경우 추가 불가
- [x] 할 일 삭제

### 추가 기능

- [ ] 할 일 필터링(진행 중, 완료 된 일, 전체)
- [ ] 해야 할 일의 총 개수 출력
- [ ] 새로고침 시에도 유지

## code convention

- 들여쓰기 depth 2 이하.
- 함수 길이 15라인 이하.
3 changes: 2 additions & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link href="./src/styles/global.css" rel="stylesheet" />
<title></title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
<script type="module" src="/src/app.js"></script>
</body>
</html>
46 changes: 46 additions & 0 deletions src/Home.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { useState } from "react";

import { Todo, ToggleTodoDone, AddTodo, RemoveTodo } from "./interfaces";
import TodoView from "./components/TodoView";
import InputTodo from "./components/InputTodo";
import Header from "./components/Header";
import "./styles/Home.css";

/**
* todo state 관련된 CRUD 함수
* @param todoList
* @param setTodoList
* @returns
*/
const TodoListFunc = (todoList: Todo[], setTodoList: React.Dispatch<React.SetStateAction<Todo[]>>) => {
const toggleTodoDone: ToggleTodoDone = index => {
todoList[index].done=!todoList[index].done;
setTodoList([...todoList]);
}
const addTodo: AddTodo = content => {
let nextId: number = todoList.length>0 ? todoList[todoList.length-1].id+1 : 0;
todoList.push({id: nextId, done: false, content: content});
setTodoList([...todoList]);
}
const removeTodo: RemoveTodo = index => {
todoList.splice(index, 1);
setTodoList([...todoList]);
}
return {toggleTodoDone, addTodo, removeTodo};
}
/**
* Todo 홈페이지 컴포넌트
* + todoList state 관리
*/
const Home = () => {
const [todoList, setTodoList] = useState<Todo[]>([]);
const {toggleTodoDone, addTodo, removeTodo} = TodoListFunc(todoList, setTodoList);

return <div className="homepage">
<Header />
<InputTodo addTodo={addTodo}/>
<TodoView todoList={todoList} toggleTodoDone={toggleTodoDone} removeTodo={removeTodo} />
</div>;
};

export default Home;
6 changes: 6 additions & 0 deletions src/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import Home from './Home';

const root = ReactDOM.createRoot(document.getElementById('app'));
root.render(React.createElement(Home));
17 changes: 17 additions & 0 deletions src/components/Header.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { useState } from "react";

import "../styles/header.css";

const Header = () => {
const [gettedAdvice, setGettedAdvice] = useState("Loading...");
fetch("https://api.adviceslip.com/advice")
.then(res => res.json())
.then(data => {setGettedAdvice(data.slip.advice)});

return <div className="header">
<h1>Todo List</h1>
<p>{gettedAdvice}</p>
</div>;
}

export default Header;
20 changes: 20 additions & 0 deletions src/components/InputTodo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { useState } from "react";

import { AddTodo } from "../interfaces";
import "../styles/inputTodo.css"

const InputTodo = ({ addTodo }: { addTodo: AddTodo }) => {
const [content, setContent] = useState<string>("");
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
if(""===content) return;
addTodo(content);
setContent("");
}
return <form className="inputTodo" onSubmit={handleSubmit}>
<input type="text" value={content} onChange={e => {setContent(e.currentTarget.value)}}/>
<button type="submit">할 일 등록</button>
</form>;
}

export default InputTodo;
22 changes: 22 additions & 0 deletions src/components/TodoView.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { RemoveTodo, Todo, ToggleTodoDone } from "../interfaces";

const TodoEl = ({ todo, index, toggleTodoDone, removeTodo }: { todo: Todo, index: number, toggleTodoDone: ToggleTodoDone, removeTodo: RemoveTodo }) => {
return <li>
<input type="checkbox" checked={todo.done} onChange={() => toggleTodoDone(index)} name={`todo${todo.id}`}/>
<label htmlFor={`todo${todo.id}`}>{todo.content}</label>
<button onClick={() => removeTodo(index)}>삭제</button>
</li>
};

/**
* todo 리스트 컴포넌트
* @param todoList todo 리스트 데이터
* @returns
*/
const TodoView = ({ todoList, toggleTodoDone, removeTodo }: { todoList:Todo[], toggleTodoDone: ToggleTodoDone, removeTodo: RemoveTodo }) => {
return <ul>{todoList.map((todo, index) =>
<TodoEl key={todo.id} todo={todo} index={index} toggleTodoDone={toggleTodoDone} removeTodo={removeTodo}/>
)}</ul>;
};

export default TodoView;
27 changes: 27 additions & 0 deletions src/interfaces.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
export interface Todo {
id: number;
done: boolean;
content: string;
}

/**
* todoList의 n번째 todo의 done 여부를 토글하는 함수
* @param index
*/
export interface ToggleTodoDone{
(index: number): void;
}
/**
* todoList에 todo 추가
* @param content
*/
export interface AddTodo{
(content: string): void;
}
/**
* todoList의 n번째 todo를 삭제하는 함수
* @param index
*/
export interface RemoveTodo{
(index: number): void;
}
Empty file removed src/main.js
Empty file.
3 changes: 3 additions & 0 deletions src/styles/Home.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.homepage{
padding: 30px;
}
171 changes: 171 additions & 0 deletions src/styles/global.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
@font-face {
font-family: 'Pretendard-Regular';
src: url('https://fastly.jsdelivr.net/gh/Project-Noonnu/noonfonts_2107@1.1/Pretendard-Regular.woff') format('woff');
font-weight: 400;
font-style: normal;
}

:root {
--main-c: #517acd;
--side-c: #c2cf7d;
}

html,
body,
div,
span,
applet,
object,
iframe,
h1,
h2,
h3,
h4,
h5,
h6,
p,
blockquote,
pre,
a,
abbr,
acronym,
address,
big,
cite,
code,
del,
dfn,
em,
img,
ins,
kbd,
q,
s,
samp,
small,
strike,
strong,
sub,
sup,
tt,
var,
b,
u,
i,
center,
dl,
dt,
dd,
ol,
ul,
li,
fieldset,
form,
label,
legend,
table,
caption,
tbody,
tfoot,
thead,
tr,
th,
td,
article,
aside,
canvas,
details,
embed,
figure,
figcaption,
footer,
header,
hgroup,
menu,
nav,
output,
ruby,
section,
summary,
time,
mark,
audio,
video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article,
aside,
details,
figcaption,
figure,
footer,
header,
hgroup,
menu,
nav,
ol,
ul {
list-style: none;
}
blockquote,
q {
quotes: none;
}
blockquote:before,
blockquote:after,
q:before,
q:after {
content: "";
content: none;
}
table {
border-collapse: collapse;
border-spacing: 0;
}

a{
color: inherit;
text-decoration: none;
}

a:hover{
text-decoration: underline;
}

button{
background: none;
color: inherit;
border: none;
padding: 0;
font: inherit;
cursor: pointer;
outline: inherit;
}

input, textarea, select{
background: var(--side-c);
color: inherit;
border: none;
font: inherit;
cursor: text;
outline: inherit;
resize: none;
}

body{
font-family: 'Pretendard-Regular';
font-size: 16px;
background-color: #111111;
color: #f9f9f9;
}
button{
padding: 5px 10px;
border: 3px solid var(--side-c);
border-radius: 10px;
}
25 changes: 25 additions & 0 deletions src/styles/header.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
.header h1{
width: fit-content;
margin-bottom: 20px;

background: linear-gradient(40deg, var(--main-c), var(--side-c));
color: transparent;
-webkit-background-clip: text;

font-weight: 800;
font-size: 30px;
}
.header p{
padding: 10px 10px 0px 10px;
margin-bottom: 20px;
border-radius: 10px;
font-style: italic;
background-color: #181818;
}
.header p::before, .header p::after{
content: "‟";
display: block;

color: var(--side-c);
font-size: 30px;
}
Loading