Skip to content

Commit 449bf81

Browse files
committed
feat: 다크모드 추가
1 parent d43e296 commit 449bf81

5 files changed

Lines changed: 104 additions & 34 deletions

File tree

vite-project/src/App.tsx

Lines changed: 31 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,34 @@
1-
import { useTodos } from "./hooks/useTodos"; // 경로 맞게 수정
2-
import { Calendar } from "./components/Calendar"; // 경로 맞게 수정
3-
import { TodoList } from "./components/TodoList";
1+
import { useTodos } from './hooks/useTodos'; // 경로 맞게 수정
2+
import { Calendar } from './components/Calendar'; // 경로 맞게 수정
3+
import { TodoList } from './components/TodoList';
4+
import { useDarkMode } from './hooks/useDarkMode';
45

56
export default function App() {
6-
const { todos, selectedDate, setSelectedDate, addTodo, toggleTodo, deleteTodo } = useTodos();
7+
const {
8+
todos,
9+
selectedDate,
10+
setSelectedDate,
11+
addTodo,
12+
toggleTodo,
13+
deleteTodo,
14+
} = useTodos();
15+
const { isDarkMode, toggleDarkMode } = useDarkMode();
716

817
return (
9-
<div className="w-full min-h-screen p-10 font-sans">
10-
<h1 className="text-2xl font-bold mb-6">Todo List</h1>
11-
18+
<div className="w-full min-h-screen p-10 font-sans dark:bg-darkbg dark:text-white">
19+
<nav className="flex justify-between w-full text-2xl font-bold mb-6">
20+
<h1>Todo List</h1>
21+
22+
<button
23+
onClick={toggleDarkMode}
24+
className={`w-8 h-8 rounded-[5px] cursor-pointer transition-colors duration-200
25+
${isDarkMode ? 'bg-deepBlue' : 'bg-cream'}
26+
`}
27+
>
28+
{isDarkMode ? '🌙' : '☀️'}
29+
</button>
30+
</nav>
31+
1232
<div className="flex flex-wrap gap-8">
1333
<section className="flex-1 min-w-[300px] rounded-lg">
1434
<Calendar
@@ -18,19 +38,16 @@ export default function App() {
1838
/>
1939
</section>
2040

21-
<section className="flex-1 min-w-[300px] p-6 border border-gray-200 rounded-lg">
22-
<h2 className="text-xl font-semibold mb-4">{selectedDate} 할 일 목록</h2>
23-
<div className="text-gray-500 mt-4">
24-
<TodoList
41+
<section className="flex-1 min-w-[300px] p-6 rounded-lg border border-px dark:border-none dark:bg-graybg shadow border-gray-200">
42+
<TodoList
2543
todos={todos}
2644
selectedDate={selectedDate}
2745
addTodo={addTodo}
2846
toggleTodo={toggleTodo}
2947
deleteTodo={deleteTodo}
30-
/>
31-
</div>
48+
/>
3249
</section>
3350
</div>
3451
</div>
3552
);
36-
}
53+
}

vite-project/src/components/Calendar.tsx

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { useState } from 'react';
22
import type { TodoData } from '../../types/todo';
3-
import { getTodayDateString } from '../hooks/useTodos.ts'; // 경로 맞게 수정
3+
import { getTodayDateString } from '../hooks/useTodos.ts';
44

55
interface CalendarProps {
66
todos: TodoData;
@@ -42,17 +42,17 @@ export const Calendar = ({
4242
const weekDays = ['일', '월', '화', '수', '목', '금', '토'];
4343

4444
return (
45-
<div className="border border-gray-200 rounded-lg p-5 bg-white shadow-sm">
45+
<div className="border border-gray-200 dark:border-none rounded-lg p-5 bg-white dark:bg-graybg dark:text-cream shadow-sm">
4646
{/* 1. 헤더 영역 */}
4747
<header className="mb-6">
48-
<div className="text-sm text-gray-500 mb-6">
48+
<div className="text-sm text-gray-500 dark:text-cream mb-6">
4949
오늘: {today.getFullYear()}{today.getMonth() + 1}{' '}
5050
{today.getDate()}
5151
</div>
5252
<div className="flex justify-between items-center">
5353
<button
5454
onClick={handlePrevMonth}
55-
className="px-3 py-1 bg-gray-100 rounded-md hover:bg-gray-200 transition-colors cursor-pointer"
55+
className="px-3 py-1 bg-gray-100 dark:bg-darkbg rounded-md hover:bg-gray-200 transition-colors cursor-pointer"
5656
>
5757
&lt;
5858
</button>
@@ -61,7 +61,7 @@ export const Calendar = ({
6161
</h2>
6262
<button
6363
onClick={handleNextMonth}
64-
className="px-3 py-1 bg-gray-100 rounded-md hover:bg-gray-200 transition-colors cursor-pointer"
64+
className="px-3 py-1 bg-gray-100 dark:bg-darkbg rounded-md hover:bg-gray-200 transition-colors cursor-pointer"
6565
>
6666
&gt;
6767
</button>
@@ -73,7 +73,15 @@ export const Calendar = ({
7373
{weekDays.map((day) => (
7474
<div
7575
key={day}
76-
className={`font-semibold py-2 ${day === '일' ? 'text-red-600' : day === '토' ? 'text-blue-600' : 'text-gray-600'}`}
76+
className={`font-semibold py-2
77+
${
78+
day === '일'
79+
? 'text-red-600 dark:text-red-400'
80+
: day === '토'
81+
? 'text-blue-600 dark:text-blue-400'
82+
: 'text-gray-600 dark:text-gray-300'
83+
}
84+
`}
7785
>
7886
{day}
7987
</div>
@@ -96,34 +104,41 @@ export const Calendar = ({
96104
const incompleteCount = dayTodos.filter(
97105
(todo) => !todo.isCompleted
98106
).length;
107+
const completedCount = dayTodos.length - incompleteCount;
99108

100109
return (
101110
<div
102111
key={dateString}
103112
onClick={() => setSelectedDate(dateString)}
104113
className={`
105-
min-h-[80px] p-2 rounded-md flex flex-col items-center cursor-pointer transition-all border
114+
min-h-[80px] p-2 rounded-md flex flex-col items-center cursor-pointer transition-all dark:text-white border border-px dark:border-gray-600
106115
${
107116
isSelected
108-
? 'border-blue-500 bg-blue-50 ring-1 ring-blue-500'
109-
: 'border-gray-100 hover:bg-gray-50'
117+
? 'border-blue-500 dark:border-deepBlue bg-blue-50 dark:bg-deepBlue ring-1 ring-blue-500'
118+
: 'border-gray-100 hover:bg-gray-50 dark:hover:bg-deepBlue'
110119
}
111120
`}
112121
>
113122
<div
114-
className={`font-medium ${isToday ? 'text-midBlue' : 'text-gray-800'}`}
123+
className={`font-medium dark:text-white ${isToday ? 'text-midBlue' : 'text-gray-800'}`}
115124
>
116125
{day}
117126
</div>
118127

119128
{isToday && (
120-
<span className="text-[10px] font-bold text-midBlue leading-none mt-1">
129+
<span className="text-[10px] font-bold text-midBlue leading-none mt-1 mb-1">
121130
today
122131
</span>
123132
)}
124133

134+
{completedCount > 0 && (
135+
<div className="text-xs text-deepBlue font-medium">
136+
완료: {completedCount}
137+
</div>
138+
)}
139+
125140
{incompleteCount > 0 && (
126-
<div className="text-xs text-deepBlue font-medium mt-auto mb-1">
141+
<div className="text-xs text-deepBlue font-medium">
127142
미완료: {incompleteCount}
128143
</div>
129144
)}

vite-project/src/components/TodoList.tsx

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,16 +25,20 @@ export const TodoList = ({
2525
setUserInput(e.target.value);
2626
};
2727

28-
const handleSubmit = () => {
28+
// React.FormEvent는 이제 사용 비권장이 됐습니다. 공식 문서는 노션에 업로드해놓을테니 확인해봐도 좋을 것 같습니다.
29+
const handleSubmit = (e: React.SubmitEvent) => {
30+
e.preventDefault();
31+
2932
if (!userInput.trim()) return;
3033

3134
addTodo(selectedDate, userInput);
3235
setUserInput('');
3336
};
3437

3538
return (
36-
<div className="flex flex-col gap-4">
37-
<div className="flex gap-2 h-[40px]">
39+
<div className="flex flex-col gap-4 mt-4">
40+
<h2 className="text-xl text-black dark:text-cream font-semibold mb-4">{selectedDate} 할 일 목록</h2>
41+
<form onSubmit={handleSubmit} className="flex gap-2 h-[40px]">
3842
<input
3943
type="text"
4044
value={userInput}
@@ -43,30 +47,37 @@ export const TodoList = ({
4347
placeholder="새로운 할 일을 입력하세요"
4448
/>
4549
<button
46-
onClick={handleSubmit}
50+
type="submit"
4751
className="bg-midBlue w-[100px] text-white px-4 py-2 rounded cursor-pointer"
4852
>
4953
추가
5054
</button>
51-
</div>
55+
</form>
5256

5357
<ul className="flex flex-col gap-2 mt-4">
5458
{currentTodos.map((todo) => (
5559
<li
5660
key={todo.id}
5761
className="flex justify-between items-center border-b pb-2"
5862
>
59-
<span className={`${todo.isCompleted === true ? 'text-gray-400 font-semibold line-through' : 'text-black font-semibold'}`}>{todo.title}</span>
63+
<span
64+
className={`${todo.isCompleted === true ? 'text-gray-400 font-semibold line-through' : 'text-black dark:text-cream font-semibold'}`}
65+
>
66+
{todo.title}
67+
</span>
6068
<div className="flex gap-2 h-10">
6169
{!todo.isCompleted && (
6270
<button
6371
onClick={() => toggleTodo(selectedDate, todo.id)}
64-
className="w-[80px] border bg-deepBlue text-white rounded-lg cursor-pointer"
72+
className="w-[80px] border dark:border-none bg-deepBlue text-white rounded-lg cursor-pointer"
6573
>
6674
완료
6775
</button>
6876
)}
69-
<button onClick={() => deleteTodo(selectedDate, todo.id)} className='w-[80px] bg-white border rounded-lg cursor-pointer'>
77+
<button
78+
onClick={() => deleteTodo(selectedDate, todo.id)}
79+
className="w-[80px] bg-white dark:bg-darkbg border dark:border-none rounded-lg cursor-pointer"
80+
>
7081
삭제
7182
</button>
7283
</div>
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { useEffect, useState } from "react"
2+
3+
export const useDarkMode = () => {
4+
const [isDarkMode, setIsDarkMode] = useState(localStorage.getItem('theme') === 'dark');
5+
6+
useEffect(() => {
7+
if(isDarkMode) {
8+
document.documentElement.classList.add('dark');
9+
localStorage.setItem('theme', 'dark');
10+
} else {
11+
document.documentElement.classList.remove('dark');
12+
localStorage.setItem('theme', 'light');
13+
}
14+
}, [isDarkMode]);
15+
16+
const toggleDarkMode = () => {
17+
setIsDarkMode(!isDarkMode);
18+
}
19+
20+
return { isDarkMode, toggleDarkMode };
21+
}

vite-project/src/index.css

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
@import 'tailwindcss';
22

3+
@custom-variant dark (&:where(.dark, .dark *));
4+
35
@theme {
46
--color-main: #f9f7f7;
7+
--color-cream: #F9F7F7;
58
--color-midBlue: #7aaace;
69
--color-skyBlue: #dbe2ef;
710
--color-deepBlue: #355872;
11+
12+
--color-darkbg: #222831;
13+
--color-graybg: #393E46;
814
}

0 commit comments

Comments
 (0)