Skip to content

Commit c154329

Browse files
committed
dark mode
1 parent 2dfb274 commit c154329

File tree

9 files changed

+102
-65
lines changed

9 files changed

+102
-65
lines changed

src/App.jsx

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import React, { useState } from 'react';
1+
import React, { useEffect, useState } from 'react';
22
import { motion, AnimatePresence } from 'framer-motion';
3-
import { PlusIcon } from '@heroicons/react/24/outline';
3+
import { PlusIcon, MoonIcon, SunIcon } from '@heroicons/react/24/outline';
44

55
import { TaskProvider } from './context/TaskContext';
66
import { TagProvider } from './context/TagContext';
@@ -11,23 +11,54 @@ import TaskBoard from './features/lists/components/TaskBoard';
1111

1212
function App() {
1313
const [showInput, setShowInput] = useState(false);
14+
const [isDarkMode, setIsDarkMode] = useState(() => {
15+
if (typeof window === 'undefined') {
16+
return false;
17+
}
18+
19+
const storedTheme = window.localStorage.getItem('task-dashboard.theme');
20+
if (storedTheme) {
21+
return storedTheme === 'dark';
22+
}
23+
24+
return window.matchMedia('(prefers-color-scheme: dark)').matches;
25+
});
26+
27+
useEffect(() => {
28+
if (typeof document !== 'undefined') {
29+
document.documentElement.classList.toggle('dark', isDarkMode);
30+
}
31+
32+
if (typeof window !== 'undefined') {
33+
window.localStorage.setItem('task-dashboard.theme', isDarkMode ? 'dark' : 'light');
34+
}
35+
}, [isDarkMode]);
1436

1537
return (
1638
<TaskProvider>
1739
<TagProvider>
1840
<ListProvider>
19-
<div className="App min-h-screen bg-gradient-to-br from-primary-50 to-secondary-50 flex flex-col items-center py-12 px-4" data-testid="app">
41+
<div className="App min-h-screen bg-gradient-to-br from-primary-50 to-secondary-50 dark:from-neutral-950 dark:via-slate-950 dark:to-neutral-900 flex flex-col items-center py-12 px-4 transition-colors duration-300" data-testid="app">
2042
<div className="w-full max-w-6xl">
2143
<motion.div
22-
className="mb-6 bg-white rounded-2xl shadow-soft p-6"
44+
className="mb-6 bg-white dark:bg-neutral-900 dark:border dark:border-neutral-800 rounded-2xl shadow-soft p-6 transition-colors duration-300"
2345
initial={{ opacity: 0, y: -20 }}
2446
animate={{ opacity: 1, y: 0 }}
2547
transition={{ duration: 0.5 }}
2648
data-testid="app-header"
2749
>
2850
<div className="flex justify-between items-center mb-6">
29-
<h1 className="text-3xl font-bold text-neutral-800 tracking-tight">Task Dashboard</h1>
30-
{/* Stats will be displayed from TaskContext */}
51+
<h1 className="text-3xl font-bold text-neutral-800 dark:text-neutral-100 tracking-tight">Task Dashboard</h1>
52+
<button
53+
type="button"
54+
className="inline-flex items-center gap-2 rounded-xl border border-neutral-200 dark:border-neutral-700 bg-white/70 dark:bg-neutral-800 px-3 py-2 text-sm font-medium text-neutral-700 dark:text-neutral-200 hover:bg-neutral-50 dark:hover:bg-neutral-700 transition-colors"
55+
onClick={() => setIsDarkMode(prev => !prev)}
56+
data-testid="theme-toggle-button"
57+
aria-label={isDarkMode ? 'Switch to light mode' : 'Switch to dark mode'}
58+
>
59+
{isDarkMode ? <SunIcon className="h-4 w-4" /> : <MoonIcon className="h-4 w-4" />}
60+
{isDarkMode ? 'Light' : 'Dark'}
61+
</button>
3162
</div>
3263

3364
<AnimatePresence>

src/features/lists/components/ListAddTask.jsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,15 +48,15 @@ function ListAddTask({ onCancel, listFilters }) {
4848
placeholder="Add a task to this list..."
4949
value={text}
5050
onChange={(e) => setText(e.target.value)}
51-
className="w-full py-2 px-4 pr-20 text-sm text-neutral-800 rounded-lg border border-neutral-200 focus:border-primary-400 focus:ring-1 focus:ring-primary-200 outline-hidden transition-all"
51+
className="w-full py-2 px-4 pr-20 text-sm text-neutral-800 dark:text-neutral-100 dark:bg-neutral-800 rounded-lg border border-neutral-200 dark:border-neutral-700 focus:border-primary-400 focus:ring-1 focus:ring-primary-200 outline-hidden transition-all"
5252
autoComplete="off"
5353
data-testid="list-task-input"
5454
/>
5555
<div className="absolute right-1.5 top-1/2 -translate-y-1/2 flex gap-1">
5656
<motion.button
5757
type="button"
5858
onClick={onCancel}
59-
className="p-1.5 text-neutral-500 hover:text-neutral-700 rounded-full hover:bg-neutral-100 transition-colors"
59+
className="p-1.5 text-neutral-500 dark:text-neutral-300 hover:text-neutral-700 dark:hover:text-neutral-100 rounded-full hover:bg-neutral-100 dark:hover:bg-neutral-700 transition-colors"
6060
whileTap={{ scale: 0.9 }}
6161
data-testid="list-cancel-button"
6262
>
@@ -81,7 +81,7 @@ function ListAddTask({ onCancel, listFilters }) {
8181
{/* Show tags that will be automatically applied */}
8282
{getTagFilters().length > 0 && (
8383
<div className="mt-2 mb-1 px-1" data-testid="auto-applied-tags">
84-
<p className="text-xs text-neutral-500">
84+
<p className="text-xs text-neutral-500 dark:text-neutral-400">
8585
Will be tagged with:
8686
<span className="font-medium ml-1 text-primary-600">
8787
{getTagFilters().join(', ')}
@@ -93,4 +93,4 @@ function ListAddTask({ onCancel, listFilters }) {
9393
);
9494
}
9595

96-
export default ListAddTask;
96+
export default ListAddTask;

src/features/lists/components/TaskBoard.jsx

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -74,10 +74,10 @@ function TaskBoard() {
7474
return (
7575
<div className="task-board" data-testid="task-board">
7676
<div className="mb-4 flex justify-between items-center">
77-
<h2 className="font-semibold text-lg text-neutral-700">Task Lists</h2>
77+
<h2 className="font-semibold text-lg text-neutral-700 dark:text-neutral-200">Task Lists</h2>
7878
<button
7979
type="button"
80-
className="flex items-center text-sm font-medium text-primary-600 hover:text-primary-800 transition-colors px-3 py-1.5 hover:bg-primary-50 rounded-lg"
80+
className="flex items-center text-sm font-medium text-primary-600 hover:text-primary-800 dark:text-primary-300 dark:hover:text-primary-200 transition-colors px-3 py-1.5 hover:bg-primary-50 dark:hover:bg-neutral-800 rounded-lg"
8181
onClick={handleManageTags}
8282
data-testid="manage-tags-button"
8383
>
@@ -120,7 +120,7 @@ function TaskBoard() {
120120
return (
121121
<div
122122
key={list.id}
123-
className="task-list-container bg-white rounded-xl shadow-soft w-full flex flex-col"
123+
className="task-list-container bg-white dark:bg-neutral-900 dark:border dark:border-neutral-800 rounded-xl shadow-soft w-full flex flex-col transition-colors duration-300"
124124
data-testid={`task-list-${list.id}`}
125125
>
126126
{editingListId === list.id ? (
@@ -131,18 +131,18 @@ function TaskBoard() {
131131
/>
132132
) : (
133133
<>
134-
<div className="list-header p-4 border-b border-neutral-100 flex justify-between items-center">
135-
<h2 className="font-medium text-lg" data-testid={`list-title-${list.id}`}>{list.title}</h2>
134+
<div className="list-header p-4 border-b border-neutral-100 dark:border-neutral-800 flex justify-between items-center">
135+
<h2 className="font-medium text-lg text-neutral-800 dark:text-neutral-100" data-testid={`list-title-${list.id}`}>{list.title}</h2>
136136
<div className="flex items-center gap-2">
137137
<span
138-
className="text-xs font-medium text-neutral-500 bg-neutral-100 px-2 py-0.5 rounded-xs"
138+
className="text-xs font-medium text-neutral-500 dark:text-neutral-300 bg-neutral-100 dark:bg-neutral-800 px-2 py-0.5 rounded-xs"
139139
data-testid={`task-count-${list.id}`}
140140
>
141141
{completedTasksCount}/{filteredTasks.length}
142142
</span>
143143
<button
144144
type="button"
145-
className="text-sm text-neutral-500 hover:text-neutral-700 px-2 py-1 hover:bg-neutral-100 rounded-xs"
145+
className="text-sm text-neutral-500 dark:text-neutral-300 hover:text-neutral-700 dark:hover:text-neutral-100 px-2 py-1 hover:bg-neutral-100 dark:hover:bg-neutral-800 rounded-xs"
146146
onClick={() => handleEditTaskList(list.id)}
147147
data-testid={`edit-list-${list.id}`}
148148
>
@@ -151,7 +151,7 @@ function TaskBoard() {
151151
{list.id !== 'default' && (
152152
<button
153153
type="button"
154-
className="text-sm text-rose-500 hover:text-rose-700 px-2 py-1 hover:bg-rose-50 rounded-xs"
154+
className="text-sm text-rose-500 hover:text-rose-700 px-2 py-1 hover:bg-rose-50 dark:hover:bg-rose-950/40 rounded-xs"
155155
onClick={() => deleteTaskList(list.id)}
156156
data-testid={`delete-list-${list.id}`}
157157
>
@@ -174,7 +174,7 @@ function TaskBoard() {
174174
<button
175175
type="button"
176176
onClick={() => handleAddTaskToList(list.id)}
177-
className="mb-3 w-full py-2 px-3 flex items-center justify-center text-sm text-neutral-600 hover:text-primary-600 bg-neutral-50 hover:bg-neutral-100 rounded-lg border border-dashed border-neutral-300 hover:border-primary-300 transition-colors"
177+
className="mb-3 w-full py-2 px-3 flex items-center justify-center text-sm text-neutral-600 dark:text-neutral-300 hover:text-primary-600 dark:hover:text-primary-300 bg-neutral-50 dark:bg-neutral-800 hover:bg-neutral-100 dark:hover:bg-neutral-700 rounded-lg border border-dashed border-neutral-300 dark:border-neutral-700 hover:border-primary-300 transition-colors"
178178
data-testid={`add-task-to-list-${list.id}`}
179179
>
180180
<PlusIcon className="h-4 w-4 mr-1.5" />
@@ -187,11 +187,11 @@ function TaskBoard() {
187187

188188
{/* List action buttons */}
189189
{filteredTasks.length > 0 && (
190-
<div className="list-actions p-3 border-t border-neutral-100 flex justify-between">
190+
<div className="list-actions p-3 border-t border-neutral-100 dark:border-neutral-800 flex justify-between">
191191
<motion.button
192192
type="button"
193193
onClick={() => handleCompleteListTasks(list.id)}
194-
className="flex items-center text-xs font-medium text-primary-600 hover:text-primary-800 transition-colors px-2 py-1 hover:bg-primary-50 rounded-lg"
194+
className="flex items-center text-xs font-medium text-primary-600 hover:text-primary-800 dark:text-primary-300 dark:hover:text-primary-200 transition-colors px-2 py-1 hover:bg-primary-50 dark:hover:bg-neutral-800 rounded-lg"
195195
disabled={allTasksCompleted}
196196
whileHover={{ scale: allTasksCompleted ? 1 : 1.02 }}
197197
whileTap={{ scale: allTasksCompleted ? 1 : 0.98 }}
@@ -204,7 +204,7 @@ function TaskBoard() {
204204
<motion.button
205205
type="button"
206206
onClick={() => handleDeleteListCompletedTasks(list.id)}
207-
className="flex items-center text-xs font-medium text-rose-500 hover:text-rose-700 transition-colors px-2 py-1 hover:bg-rose-50 rounded-lg"
207+
className="flex items-center text-xs font-medium text-rose-500 hover:text-rose-700 transition-colors px-2 py-1 hover:bg-rose-50 dark:hover:bg-rose-950/40 rounded-lg"
208208
disabled={!hasCompletedTasks}
209209
whileHover={{ scale: !hasCompletedTasks ? 1 : 1.02 }}
210210
whileTap={{ scale: !hasCompletedTasks ? 1 : 0.98 }}
@@ -224,7 +224,7 @@ function TaskBoard() {
224224
{/* Add new task list button */}
225225
<motion.button
226226
type="button"
227-
className="add-list-button h-48 rounded-xl border-2 border-dashed border-neutral-200 flex flex-col items-center justify-center text-neutral-400 hover:text-primary-600 hover:border-primary-300 transition-colors"
227+
className="add-list-button h-48 rounded-xl border-2 border-dashed border-neutral-200 dark:border-neutral-700 flex flex-col items-center justify-center text-neutral-400 dark:text-neutral-500 hover:text-primary-600 dark:hover:text-primary-300 hover:border-primary-300 transition-colors"
228228
onClick={addTaskList}
229229
whileHover={{ scale: 1.02 }}
230230
whileTap={{ scale: 0.98 }}
@@ -238,4 +238,4 @@ function TaskBoard() {
238238
);
239239
}
240240

241-
export default TaskBoard;
241+
export default TaskBoard;

src/features/lists/components/TaskListConfig.jsx

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -36,32 +36,32 @@ function TaskListConfig({ taskList, onSave, onCancel }) {
3636

3737
return (
3838
<div className="task-list-config p-4" data-testid="task-list-config">
39-
<h3 className="font-medium text-lg mb-4">Configure List</h3>
39+
<h3 className="font-medium text-lg mb-4 text-neutral-800 dark:text-neutral-100">Configure List</h3>
4040

4141
{/* List title input */}
4242
<div className="mb-4">
43-
<label className="block text-sm font-medium text-neutral-700 mb-1" htmlFor="list-title">List Title</label>
43+
<label className="block text-sm font-medium text-neutral-700 dark:text-neutral-300 mb-1" htmlFor="list-title">List Title</label>
4444
<input
4545
id="list-title"
4646
type="text"
4747
value={title}
4848
onChange={(e) => setTitle(e.target.value)}
49-
className="w-full px-3 py-2 border border-neutral-300 rounded-md focus:outline-hidden focus:ring-1 focus:ring-primary-500"
49+
className="w-full px-3 py-2 border border-neutral-300 dark:border-neutral-700 dark:bg-neutral-800 dark:text-neutral-100 rounded-md focus:outline-hidden focus:ring-1 focus:ring-primary-500"
5050
placeholder="Enter list title"
5151
data-testid="list-title-input"
5252
/>
5353
</div>
5454

5555
{/* Current filters */}
5656
<div className="mb-4">
57-
<label className="block text-sm font-medium text-neutral-700 mb-1">Current Filters</label>
57+
<label className="block text-sm font-medium text-neutral-700 dark:text-neutral-300 mb-1">Current Filters</label>
5858

5959
{filters.length === 0 ? (
60-
<p className="text-sm text-neutral-500" data-testid="no-filters-message">No filters applied. This list will show all tasks.</p>
60+
<p className="text-sm text-neutral-500 dark:text-neutral-400" data-testid="no-filters-message">No filters applied. This list will show all tasks.</p>
6161
) : (
6262
<div className="flex flex-wrap gap-2" data-testid="filter-list">
6363
{filters.map((filter, index) => (
64-
<div key={index} className="inline-flex items-center bg-neutral-100 px-3 py-1 rounded-full text-sm" data-testid={`filter-item-${index}`}>
64+
<div key={index} className="inline-flex items-center bg-neutral-100 dark:bg-neutral-800 dark:text-neutral-200 px-3 py-1 rounded-full text-sm" data-testid={`filter-item-${index}`}>
6565
{filter.type === 'tag' && (
6666
<span>Tag: {filter.value}</span>
6767
)}
@@ -71,7 +71,7 @@ function TaskListConfig({ taskList, onSave, onCancel }) {
7171
<button
7272
type="button"
7373
onClick={() => removeFilter(index)}
74-
className="ml-1 text-neutral-500 hover:text-neutral-700"
74+
className="ml-1 text-neutral-500 dark:text-neutral-400 hover:text-neutral-700 dark:hover:text-neutral-200"
7575
data-testid={`remove-filter-${index}`}
7676
>
7777
<XMarkIcon className="h-4 w-4" />
@@ -84,9 +84,9 @@ function TaskListConfig({ taskList, onSave, onCancel }) {
8484

8585
{/* Add tag filter */}
8686
<div className="mb-4">
87-
<label className="block text-sm font-medium text-neutral-700 mb-1">Filter by Tag</label>
87+
<label className="block text-sm font-medium text-neutral-700 dark:text-neutral-300 mb-1">Filter by Tag</label>
8888
{tags.length === 0 ? (
89-
<p className="text-sm text-neutral-500" data-testid="no-tags-available-message">No tags available. Add tags to tasks first.</p>
89+
<p className="text-sm text-neutral-500 dark:text-neutral-400" data-testid="no-tags-available-message">No tags available. Add tags to tasks first.</p>
9090
) : (
9191
<div className="flex flex-wrap gap-2" data-testid="available-tags">
9292
{tags.map((tag) => (
@@ -112,7 +112,7 @@ function TaskListConfig({ taskList, onSave, onCancel }) {
112112

113113
{/* Add completion filter */}
114114
<div className="mb-6">
115-
<label className="block text-sm font-medium text-neutral-700 mb-1">Filter by Status</label>
115+
<label className="block text-sm font-medium text-neutral-700 dark:text-neutral-300 mb-1">Filter by Status</label>
116116
<div className="flex gap-2">
117117
<button
118118
type="button"
@@ -148,7 +148,7 @@ function TaskListConfig({ taskList, onSave, onCancel }) {
148148
<button
149149
type="button"
150150
onClick={onCancel}
151-
className="px-4 py-2 border border-neutral-300 text-neutral-700 rounded-md hover:bg-neutral-50"
151+
className="px-4 py-2 border border-neutral-300 dark:border-neutral-700 text-neutral-700 dark:text-neutral-200 rounded-md hover:bg-neutral-50 dark:hover:bg-neutral-800"
152152
data-testid="cancel-config"
153153
>
154154
Cancel
@@ -166,4 +166,4 @@ function TaskListConfig({ taskList, onSave, onCancel }) {
166166
);
167167
}
168168

169-
export default TaskListConfig;
169+
export default TaskListConfig;

0 commit comments

Comments
 (0)