diff --git a/src/App.css b/src/App.css
index 66f8f1f..1d3d268 100644
--- a/src/App.css
+++ b/src/App.css
@@ -3,18 +3,23 @@ body {
font-family: Arial, sans-serif; /* 폰트 변경 */
display: flex;
justify-content: center;
- align-items: center;
+ align-items: flex-start; /* 상단 정렬 */
min-height: 100vh;
margin: 0;
background-color: #fff;
+ padding-top: 50px; /* 상단 여백 추가 */
}
.app-container {
max-width: 600px;
width: 100%;
padding: 20px;
+ /* 그림자에 대한 언급은 없으므로 제거하거나 유지 */
+ /* box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); */
+ background-color: #fff; /* 배경색 확실히 지정 */
}
+/* 기존 요소들 스타일 */
h1 {
font-size: 2.5rem;
font-weight: bold;
@@ -29,7 +34,7 @@ h2 {
text-align: left;
}
-/* 입력창 */
+/* 입력창 (AddTodo) */
.input-container {
margin-bottom: 10px;
}
@@ -40,7 +45,8 @@ h2 {
font-size: 1rem;
border: 2px solid #333; /* 입력창만 굵은 테두리 */
border-radius: 0;
- box-sizing: border-box;
+ box-sizing: border-box; /* 패딩과 테두리가 width에 포함되도록 */
+ outline: none; /* 포커스 시 기본 외곽선 제거 */
}
/* Add 버튼 */
@@ -54,9 +60,11 @@ h2 {
font-size: 1rem;
background-color: #000;
color: #fff;
- border: none;
+ border: none; /* Add 버튼은 테두리 없음 */
border-radius: 0;
cursor: pointer;
+ outline: none;
+ transition: background-color 0.2s ease; /* 호버 효과 부드럽게 */
}
.add-button-container button:hover {
@@ -80,9 +88,18 @@ h2 {
border: 1px solid #333; /* 얇은 테두리 */
border-radius: 0;
cursor: pointer;
+ outline: none;
+ transition: background-color 0.2s ease, color 0.2s ease, border-color 0.2s ease;
}
-.filter-buttons button:hover {
+/* 활성 필터 버튼 스타일 */
+.filter-buttons button.active {
+ background-color: #000; /* 활성 버튼은 배경 검정 */
+ color: #fff; /* 글자 흰색 */
+ border-color: #000;
+}
+
+.filter-buttons button:not(.active):hover { /* 활성 상태가 아닐 때만 호버 스타일 적용 */
background-color: #f0f0f0;
}
@@ -94,38 +111,53 @@ h2 {
text-align: left;
}
-/* 작업 목록 */
+/* 작업 목록 컨테이너 */
.task-list {
- margin-top: 10px;
+ /* margin-top: 10px; */
}
+/* 각 할 일 항목 (기본 & 편집 공통 스타일) */
.task-item {
- margin-bottom: 20px;
+ margin-bottom: 20px; /* 항목 하단 간격 */
+ border-bottom: 1px solid #eee; /* 항목 구분선 */
+ padding-bottom: 20px; /* 구분선과 내용 간 간격 */
}
-.task-row {
+.task-item:last-child {
+ border-bottom: none; /* 마지막 항목은 구분선 없음 */
+ padding-bottom: 0;
+}
+
+
+/* 기본 모드 (.task-item 안에 있을 때) */
+.task-item .task-row {
display: flex;
align-items: center;
- margin-bottom: 5px;
+ margin-bottom: 5px; /* 체크박스/이름과 버튼 간 간격 */
}
-.task-row input[type="checkbox"] {
+/* 체크박스 기본 스타일 */
+.task-item .task-row input[type="checkbox"] {
width: 20px;
height: 20px;
margin-right: 10px;
border: 2px solid #333;
border-radius: 0;
- appearance: none;
+ appearance: none; /* 기본 브라우저 스타일 제거 */
cursor: pointer;
+ flex-shrink: 0; /* 체크박스가 줄어들지 않도록 */
+ position: relative; /* 체크마크 위치 기준 */
+ outline: none;
}
-.task-row input[type="checkbox"]:checked {
+/* 체크박스 체크된 상태 스타일 */
+.task-item .task-row input[type="checkbox"]:checked {
background-color: #000;
border-color: #000;
- position: relative;
}
-.task-row input[type="checkbox"]:checked::after {
+/* 체크박스 체크마크 */
+.task-item .task-row input[type="checkbox"]:checked::after {
content: "✔";
color: #fff;
font-size: 14px;
@@ -135,36 +167,125 @@ h2 {
transform: translate(-50%, -50%);
}
-.task-row .task-name {
+/* 할 일 이름 */
+.task-item .task-row .task-name {
font-size: 1rem;
+ flex-grow: 1; /* 남은 공간 차지 */
+ word-break: break-word; /* 긴 단어 줄바꿈 */
+}
+
+/* 완료된 할 일 이름 스타일 */
+.task-item .task-row .task-name.completed {
+ text-decoration: line-through;
+ color: #888; /* 회색으로 변경 */
}
-.task-actions {
+
+/* Edit/Delete 버튼 컨테이너 (기본 모드 & 편집 모드 공통) */
+.task-item .task-actions {
display: flex;
- gap: 10px;
+ gap: 10px; /* 버튼 간 간격 */
}
-.task-actions button {
- flex: 1;
+/* Edit/Delete 버튼 기본 스타일 */
+.task-item .task-actions button {
+ flex: 1; /* 필터 버튼과 동일한 폭 */
padding: 5px 10px;
font-size: 0.9rem;
background-color: #fff;
color: #000;
- border: 1px solid #333;
+ border: 1px solid #333; /* 얇은 테두리 */
border-radius: 0;
cursor: pointer;
+ outline: none;
+ transition: background-color 0.2s ease, color 0.2s ease, border-color 0.2s ease;
}
-.task-actions button:hover {
+.task-item .task-actions button:hover {
background-color: #f0f0f0;
}
-.task-actions .button-delete {
- background-color: #ff0000;
- color: #fff;
-
+/* Delete 버튼 스타일 */
+.task-item .task-actions button:last-child {
+ background-color: #ff0000; /* 빨간색 배경 */
+ color: #fff; /* 흰색 글씨 */
+ border-color: #ff0000; /* 배경과 동일한 색 테두리 */
+}
+
+.task-item .task-actions button:last-child:hover {
+ background-color: #e60000; /* 호버 시 약간 어두운 빨간색 */
+ border-color: #e60000;
+}
+
+
+/* --- 👇 편집 모드 (Editing Mode) 스타일 --- */
+
+/* 편집 모드일 때 할 일 항목 컨테이너 레이아웃 */
+.task-item.editing {
+ display: flex; /* Flexbox 사용 */
+ flex-direction: column; /* 자식 요소를 세로로 쌓음 */
+ align-items: flex-start; /* 왼쪽 정렬 */
+}
+
+/* 편집 모드일 때 기본 .task-row (체크박스 + 이름) 숨기기 */
+.task-item.editing .task-row {
+ display: none;
+}
+
+/* 편집 모드 라벨 스타일 ("New name for...") */
+.task-item.editing .edit-label {
+ font-size: 1rem; /* task-name과 동일한 크기 */
+ margin-bottom: 5px; /* 라벨과 입력창 간 간격 */
+ font-weight: normal; /* 볼드 해제 (task-name은 볼드 아니었으므로) */
+}
+
+/* 편집 모드 입력창 스타일 */
+.task-item.editing input.edit-input {
+ width: 100%; /* 부모 컨테이너 폭에 맞춤 */
+ padding: 8px;
+ font-size: 1rem;
+ border: 2px solid #333;
+ border-radius: 0;
+ box-sizing: border-box;
+ margin-bottom: 10px; /* 입력창과 버튼 간 간격 */
+ outline: none; /* 포커스 시 기본 외곽선 제거 */
+}
+
+/* 편집 모드 버튼 컨테이너 스타일 (Cancel/Save) */
+.task-item.editing .task-actions {
+ display: flex; /* 버튼들은 Flex로 */
+ gap: 10px; /* 버튼 간 간격 */
+ width: 100%; /* 부모(task-item.editing) 너비에 맞춤 */
+}
+
+/* Cancel 버튼 스타일 */
+.task-item.editing .task-actions .cancel-button { /* 클래스 선택자 사용 */
+ flex: 1; /* 동일 폭 */
+ padding: 5px 10px;
+ font-size: 0.9rem;
+ background-color: #fff; /* 흰색 배경 */
+ color: #000; /* 검은색 글씨 */
+ border: 1px solid #333; /* 얇은 테두리 */
+ border-radius: 0;
+}
+.task-item.editing .task-actions .cancel-button:hover {
+ background-color: #f0f0f0;
+}
+
+/* Save 버튼 스타일 */
+.task-item.editing .task-actions .save-button { /* 클래스 선택자 사용 */
+ flex: 1; /* 동일 폭 */
+ padding: 5px 10px;
+ font-size: 0.9rem;
+ /* 👇 여기! Save 버튼 배경을 검정색으로 변경 */
+ background-color: #000; /* 검은색 배경 */
+ color: #fff; /* 글자색 흰색 유지 */
+ border: none; /* 테두리 없음 유지 */
+ /* 👆 여기까지 수정 */
+ border-radius: 0;
+}
+/* Save 버튼 호버 스타일 */
+.task-item.editing .task-actions .save-button:hover {
+ background-color: #333; /* 호버 시 약간 밝은 검정색 */
}
-.task-actions .button-delete:hover {
- background-color: #e60000;
-}
\ No newline at end of file
diff --git a/src/App.jsx b/src/App.jsx
index 755756d..4aa79b6 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -1,42 +1,70 @@
-
-import Header from "@/component/Header";
-import AddTodo from "@/component/AddTodo";
-import Category from "@/component/Category";
-import TodoList from "@/component/TodoList";
-
+// src/App.jsx - 이전 답변에서 제공된 최종 코드 사용
+import React, { useState, useEffect } from "react";
+import Header from "./component/Header";
+import AddTodo from "./component/AddTodo";
+import Category from "./component/Category";
+import TodoList from "./component/TodoList";
import "./App.css";
+const LOCAL_STORAGE_KEY = "react-todo-app.tasks";
+
function App() {
- const [tasks, setTasks] = useState([
- { name: "Eat", completed: true },
- { name: "Sleep", completed: false },
- { name: "Repeat", completed: false },
- ]);
+ const [tasks, setTasks] = useState(() => {
+ try {
+ const storedTasks = localStorage.getItem(LOCAL_STORAGE_KEY);
+ return storedTasks ? JSON.parse(storedTasks) : [];
+ } catch (error) {
+ console.error("Failed to load tasks from localStorage:", error);
+ return [];
+ }
+ });
+
const [inputValue, setInputValue] = useState("");
const [filter, setFilter] = useState("all");
+ const [displayTaskCount, setDisplayTaskCount] = useState(tasks.length);
+
+ useEffect(() => {
+ try {
+ localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(tasks));
+ } catch (error) {
+ console.error("Failed to save tasks to localStorage:", error);
+ }
+ }, [tasks]);
+
const addTask = () => {
- if (inputValue) {
- setTasks([...tasks, { name: inputValue, completed: false }]);
+ if (inputValue.trim()) {
+ const newTask = {
+ id: Date.now() + Math.random(),
+ name: inputValue.trim(),
+ completed: false,
+ };
+ setTasks([...tasks, newTask]);
+ setDisplayTaskCount(prevCount => prevCount + 1);
setInputValue("");
}
};
- const toggleTask = (index) => {
- const updatedTasks = [...tasks];
- updatedTasks[index].completed = !updatedTasks[index].completed;
+ const toggleTask = (id) => {
+ const updatedTasks = tasks.map(task =>
+ task.id === id ? { ...task, completed: !task.completed } : task
+ );
setTasks(updatedTasks);
};
- const deleteTask = (index) => {
- const updatedTasks = tasks.filter((_, i) => i !== index);
+ const deleteTask = (id) => {
+ const updatedTasks = tasks.filter(task => task.id !== id);
setTasks(updatedTasks);
+ setDisplayTaskCount(prevCount => prevCount - 1);
};
- const editTask = (index, newName) => {
- const updatedTasks = [...tasks];
- updatedTasks[index].name = newName;
- setTasks(updatedTasks);
+ const editTask = (id, newName) => {
+ if (newName && newName.trim()) {
+ const updatedTasks = tasks.map(task =>
+ task.id === id ? { ...task, name: newName.trim() } : task
+ );
+ setTasks(updatedTasks);
+ }
};
const filteredTasks = tasks.filter((task) => {
@@ -60,6 +88,7 @@ function App() {
toggleTask={toggleTask}
editTask={editTask}
deleteTask={deleteTask}
+ activeCount={displayTaskCount}
/>
);
diff --git a/src/component/AddTodo.jsx b/src/component/AddTodo.jsx
index 4207b6b..cfd0715 100644
--- a/src/component/AddTodo.jsx
+++ b/src/component/AddTodo.jsx
@@ -10,7 +10,9 @@ function AddTodo({ inputValue, setInputValue, addTask }) {
onChange={(e) => setInputValue(e.target.value)}
/>
-