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
114 changes: 93 additions & 21 deletions src/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -26,19 +26,36 @@ body {
align-items: center;
gap: 12px;
align-self: stretch;

border-radius: 12px;
background: #FFF;
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.10);
background: #fff;
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1);
justify-content: space-between;
}

.deleteButton {
background: none;
border: none;
cursor: pointer;
font-size: 18px;
transition: opacity 0.2s;

color: #0a0a0a;
text-align: center;
font-family: Pretendard;
font-size: 20px;
font-style: normal;
font-weight: 500;
line-height: 30px; /* 150% */
}

.TodoCard-text {
color: #1F2937;
font-family: 'Pretendard Variable', 'Pretendard', sans-serif;
color: #1f2937;
font-family: "Pretendard Variable", "Pretendard", sans-serif;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 21px; /* 150% */
flex: 1;
}

.TodoCard-checkbox {
Expand All @@ -48,29 +65,29 @@ body {
justify-content: center;
align-items: center;

border-radius: 13421800px;
border: 2px solid #D1D5DB;
border-radius: 50%;
border: 2px solid #d1d5db;
}

.TodoCard-checkbox.checked {
border-radius: 13421800px;
border: 2px solid #3B82F6;
background: #3B82F6; /* 테두리 없앰 */
border-radius: 50%;
border: 2px solid #3b82f6;
background: #3b82f6; /* 테두리 없앰 */
}

.TodoCard-text.completed {
color: #9CA3AF;
color: #9ca3af;
text-decoration: line-through;
font-family: 'Pretendard Variable', 'Pretendard', sans-serif;
font-family: "Pretendard Variable", "Pretendard", sans-serif;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 21px; /* 150% */
}

.title {
color: #1F2937;
font-family: 'Pretendard Variable', 'Pretendard', sans-serif;
color: #1f2937;
font-family: "Pretendard Variable", "Pretendard", sans-serif;
font-size: 24px;
font-style: normal;
font-weight: 700;
Expand All @@ -84,12 +101,11 @@ body {
align-items: stretch;
gap: 16px;
flex: 1 0 0;


list-style: none;
padding: 0;
padding: 0;
margin: 0;
}
}

.container {
display: flex;
Expand Down Expand Up @@ -124,11 +140,11 @@ body {
border-radius: 12px;
gap: 16px;
/* 부모 너비에 맞게 꽉 채우기 */
align-self: stretch;
align-self: stretch;
}

.empty-icon {
color: #0A0A0A;
color: #0a0a0a;
text-align: center;
font-family: Pretendard;
font-size: 48px;
Expand All @@ -138,11 +154,67 @@ body {
}

.empty-text {
color: #6B7280;
color: #6b7280;
text-align: center;
font-family: Pretendard;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 21px; /* 150% */
}
}

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

.todoInput {
display: flex;
height: 46.6px;
padding: 12px 16px;
align-items: center;
flex: 1 0 0;

border-radius: 8px;
border: 0.8px solid #e5e7eb;
outline: none;
box-sizing: border-box;
background-color: #f5f5f5;
}

.todoInput:focus {
display: flex;
align-items: center;

border-radius: 8px;
border: 2px solid #3b82f6;
background: rgba(255, 255, 255, 0);
box-shadow: 0 0 0 4px #3b82f6;
}

.addButton {
color: #fff;
text-align: center;
font-family: "Pretendard Variable", "Pretendard", sans-serif;
font-size: 14px;
font-style: normal;
font-weight: 500;
line-height: 21px; /* 150% */

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;
}
59 changes: 50 additions & 9 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,67 @@
import TodoHeader from "./TodoHeader"
import TodoList from "./TodoList"
import './App.css'
import { useState } from "react";
import TodoHeader from "./TodoHeader";
import TodoList from "./TodoList";
import "./App.css";

const todos = [
const fixedTodos = [
{ id: 1, text: "리액트 공식문서 읽기", isCompleted: true },
{ id: 2, text: "알고리즘 문제 풀기", isCompleted: true },
{ id: 3, text: "운동 30분 하기", isCompleted: false },
{ id: 4, text: "프로젝트 회의 준비", isCompleted: false },
{ id: 4, text: "프로젝트 회의 준비", isCompleted: false },
{ id: 5, text: "장보기 하기", isCompleted: false },
];

function App() {

const [todos, setTodos] = useState(fixedTodos);
const [inputValue, setInputValue] = useState("");

const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setInputValue(e.target.value);
};

const handleAddTodo = () => {
if (inputValue.trim() === "") return;

const newTodo = {
id: Date.now(),
text: inputValue,
isCompleted: false,
};

setTodos([...todos, newTodo]);

setInputValue("");
};

const handleDeleteTodo = (id: number) => {
const nextTodos = todos.filter((todo) => todo.id !== id);
setTodos(nextTodos);
};

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

<div className="inputContainer">
<input
className="todoInput"
type="text"
placeholder="할 일을 입력하세요"
value={inputValue}
onChange={handleInputChange}
/>
<button className="addButton" onClick={handleAddTodo}>
추가
</button>
</div>

<div className="container">
<TodoList todos={todos} />
<TodoList todos={todos} onDelete={handleDeleteTodo} />
</div>
</div>
)
);
}

export default App
export default App;
18 changes: 15 additions & 3 deletions src/CheckIcon.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,19 @@
export default function CheckIcon() {
return (
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="10" viewBox="0 0 14 10" fill="none">
<path d="M1 5L5 9L13 1" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<svg
xmlns="http://www.w3.org/2000/svg"
width="14"
height="10"
viewBox="0 0 14 10"
fill="none"
>
<path
d="M1 5L5 9L13 1"
stroke="white"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
}
}
36 changes: 24 additions & 12 deletions src/TodoCard.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,26 @@
import CheckIcon from "./CheckIcon";

export default function TodoCard({text, isCompleted}: {text: string; isCompleted: boolean}) {
return(
<div className="TodoCard">
<div className={`TodoCard-checkbox ${isCompleted ? "checked" : ""}`}>
{isCompleted && <CheckIcon />}
</div>
<div className={`TodoCard-text ${isCompleted ? 'completed' : ''}`}>
{text}
</div>
</div>
)
}
export default function TodoCard({
text,
isCompleted,
onDelete,
}: {
text: string;
isCompleted: boolean;
onDelete: () => void;
}) {
return (
<div className="TodoCard">
<div className={`TodoCard-checkbox ${isCompleted ? "checked" : ""}`}>
{isCompleted && <CheckIcon />}
</div>
<div className={`TodoCard-text ${isCompleted ? "completed" : ""}`}>
{text}
</div>

<button className="deleteButton" onClick={onDelete}>
🗑️
</button>
</div>
);
}
32 changes: 21 additions & 11 deletions src/TodoList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,33 @@ interface Todo {
isCompleted: boolean;
}

export default function TodoList({todos}: {todos: Todo[]}) {
if (todos.length === 0) {
return (
<div className="empty-state">
<span className="empty-icon">📋</span>
export default function TodoList({
todos,
onDelete,
}: {
todos: Todo[];
onDelete: (id: number) => void;
}) {
if (todos.length === 0) {
return (
<div className="empty-state">
<span className="empty-icon">📋</span>
<p className="empty-text">아직 할 일이 없어요</p>
</div>
)
}
</div>
);
}

return (
<ul className="frame1">
{todos.map((todo) => (
<li key={todo.id}>
<TodoCard text={todo.text} isCompleted={todo.isCompleted} />
<TodoCard
text={todo.text}
isCompleted={todo.isCompleted}
onDelete={() => onDelete(todo.id)}
/>
</li>
))}
</ul>
)
}
);
}