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
77 changes: 77 additions & 0 deletions src/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -131,4 +131,81 @@
font-style: normal;
font-weight: 400;
line-height: 21px;
}

.input-row {
display: flex;
width: 640px;
height: 46.6px;
padding-right: 0;
align-items: flex-start;
gap: 12px;
}

.todo-input {
display: flex;
height: 46.6px;
padding: 12px 16px;
align-items: center;
flex: 1 0 0;
border-radius: 8px;
border: 0.8px solid #e5e7eb;
font-size: 14px;
outline: none;
box-sizing: border-box;
}

.todo-input:focus {
border-color: #3b82f6;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.15);
}

.add-button {
display: flex;
width: 72.2px;
padding: 12.8px 23.2px 12.8px 24px;
justify-content: center;
align-items: center;
flex-shrink: 0;
border-radius: 8px;
background: #3B82F6;
border: none;
cursor: pointer;
color: #FFF;
text-align: center;
font-family: Pretendard;
font-size: 14px;
font-style: normal;
font-weight: 500;
line-height: 21px;

white-space: nowrap;
}

.add-button:hover {
background: #2563eb;
}

.delete-button {
margin-left: auto;
background: none;
border: none;
cursor: pointer;
font-size: 16px;
padding: 4px;
opacity: 0.5;
}

.delete-button:hover {
opacity: 1;
}

.empty-text {
color: #0A0A0A;
text-align: center;
font-family: Pretendard;
font-size: 20px;
font-style: normal;
font-weight: 500;
line-height: 30px;
}
67 changes: 59 additions & 8 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,80 @@
import "./App.css";
import TodoHeader from "./TodoHeader";
import TodoList from "./TodoList";
import { todos as initialTodos } from "./todos.data";
import InputTodo from "./InputTodo";
import { useState } from "react";
import type { Todo } from "./types/todo.types";

function App() {
const todos = [
{ id: 1, text: "리액트 공식문서 읽기", isDone: true },
{ id: 2, text: "알고리즘 문제 풀기", isDone: true },
{ id: 3, text: "운동 30분 하기", isDone: false },
{ id: 4, text: "프로젝트 회의 준비", isDone: false },
];
// 1번 오늘의 할 일
const [todos, setTodos] = useState<Todo[]>(initialTodos);
const [input, setInput] = useState("");

// 2번 오늘의 할 일
const [focusTodos, setFocusTodos] = useState<Todo[]>([]);
const [focusInput, setFocusInput] = useState("");

// 1번 추가
const handleAdd = () => {
if (input.trim() === "") return;
setTodos([
...todos,
{ id: Date.now(), text: input.trim(), isDone: false },
]);
setInput("");
};

// 2번 추가
const handleAddFocus = () => {
if (focusInput.trim() === "") return;
setFocusTodos([
...focusTodos,
{ id: Date.now(), text: focusInput.trim(), isDone: false },
]);
setFocusInput("");
};

// 1번 삭제
const handleDelete = (id: number) => {
setTodos(todos.filter((todo) => todo.id !== id));
};

// 2번 삭제
const handleDeleteFocus = (id: number) => {
setFocusTodos(focusTodos.filter((todo) => todo.id !== id));
};

return (
<div className="frame3">
<div className="frame2">
<TodoHeader />
</div>

<InputTodo
input={input}
setInput={setInput}
onAdd={handleAdd}
placeholder="할 일을 입력하세요"
/>

<div className="container">
<TodoList todos={todos} />
<TodoList todos={todos} onDelete={handleDelete} />
</div>

<div className="frame2">
<TodoHeader />
</div>

<InputTodo
input={focusInput}
setInput={setFocusInput}
onAdd={handleAddFocus}
placeholder="새로운 할 일"
/>

<div className="container">
<TodoList todos={[]} />
<TodoList todos={focusTodos} onDelete={handleDeleteFocus} />
</div>
</div>
);
Expand Down
30 changes: 30 additions & 0 deletions src/InputTodo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
interface InputTodoProps {
input: string;
setInput: (value: string) => void;
onAdd: () => void;
placeholder: string;
}

function InputTodo({ input, setInput, onAdd, placeholder }: InputTodoProps) {
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Enter") onAdd();
};

return (
<div className="input-row">
<input
className="todo-input"
type="text"
placeholder={placeholder}
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyDown={handleKeyDown}
/>
<button className="add-button" onClick={onAdd}>
추가
</button>
</div>
);
}

export default InputTodo;
3 changes: 2 additions & 1 deletion src/TodoCard.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
function TodoCard({ text, isDone }: { text: string; isDone: boolean }) {
function TodoCard({ text, isDone, onDelete }: { text: string; isDone: boolean; onDelete: () => void }) {
return (
<div className="card">
<div className={`checkbox ${isDone ? "done" : ""}`}>
Expand All @@ -15,6 +15,7 @@ function TodoCard({ text, isDone }: { text: string; isDone: boolean }) {
)}
</div>
<div className={`card-text ${isDone ? "done" : ""}`}>{text}</div>
<button className="delete-button" onClick={onDelete}>🗑️</button>
</div>
);
}
Expand Down
12 changes: 3 additions & 9 deletions src/TodoList.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,7 @@
import TodoCard from "./TodoCard";
import type { Todo } from "./types/todo.types";

interface Todo {
id: number;
text: string;
isDone: boolean;
}

function TodoList({ todos }: { todos: Todo[] }) {
// 빈 배열이면 빈 상태 UI 렌더링
function TodoList({ todos, onDelete }: { todos: Todo[]; onDelete: (id: number) => void }) {
if (todos.length === 0) {
return (
<div className="empty-container">
Expand All @@ -21,7 +15,7 @@ function TodoList({ todos }: { todos: Todo[] }) {
<ul className="frame1">
{todos.map((todo) => (
<li key={todo.id}>
<TodoCard text={todo.text} isDone={todo.isDone} />
<TodoCard text={todo.text} isDone={todo.isDone} onDelete={() => onDelete(todo.id)} />
</li>
))}
</ul>
Expand Down
9 changes: 9 additions & 0 deletions src/todos.data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import type { Todo } from "./types/todo.types";

export const todos: Todo[] = [
{ id: 1, text: "리액트 공식문서 읽기", isDone: true },
{ id: 2, text: "알고리즘 문제 풀기", isDone: true },
{ id: 3, text: "운동 30분 하기", isDone: false },
{ id: 4, text: "프로젝트 회의 준비", isDone: false },
{ id: 5, text: "장보기 하기", isDone: false },
];
5 changes: 5 additions & 0 deletions src/types/todo.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export interface Todo {
id: number;
text: string;
isDone: boolean;
}