Skip to content

Commit 4cbc919

Browse files
committed
feat(frontend/components): add theme toggle and dark mode support
Implemented a theme toggle button to switch between light, dark, and system themes. Updated various components to support dark mode styling, enhancing user experience across the application. Modified files (12): - globals.css: Added dark mode styles and variables - layout.tsx: Integrated ThemeProvider for theme management - ChatHeader.tsx: Included ThemeToggle component - ErrorToast.tsx: Updated styles for dark mode - SuggestionsBar.tsx: Enhanced styles for dark mode - ConversationItem.tsx: Adjusted styles for dark mode - ConversationList.tsx: Updated loading and empty states for dark mode - ConversationSidebar.tsx: Improved sidebar styles for dark mode - DeleteConfirmModal.tsx: Enhanced modal styles for dark mode - LoadingIndicator.tsx: Updated loading indicator styles for dark mode - ThemeToggle.tsx: New component for theme switching
1 parent 8cf5640 commit 4cbc919

13 files changed

Lines changed: 211 additions & 55 deletions

File tree

src/frontend/task-agent-web/app/globals.css

Lines changed: 67 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,19 @@
11
@import "tailwindcss";
22

3+
/* Configure dark mode to use class strategy instead of media query */
4+
@custom-variant dark (&:where(.dark, .dark *));
5+
36
:root {
47
--background: #ffffff;
58
--foreground: #171717;
69
}
710

11+
/* Dark mode CSS variables (activated by .dark class on html) */
12+
.dark {
13+
--background: #0a0a0a;
14+
--foreground: #ededed;
15+
}
16+
817
@theme inline {
918
--color-background: var(--background);
1019
--color-foreground: var(--foreground);
@@ -16,13 +25,6 @@
1625
--animate-bounce: bounce 1s infinite;
1726
}
1827

19-
@media (prefers-color-scheme: dark) {
20-
:root {
21-
--background: #0a0a0a;
22-
--foreground: #ededed;
23-
}
24-
}
25-
2628
body {
2729
color: var(--foreground);
2830
font-family: Arial, Helvetica, sans-serif;
@@ -99,6 +101,21 @@ html {
99101
background: #555;
100102
}
101103

104+
/* Dark mode scrollbar */
105+
@media (prefers-color-scheme: dark) {
106+
.overflow-auto::-webkit-scrollbar-track {
107+
background: #1f2937;
108+
}
109+
110+
.overflow-auto::-webkit-scrollbar-thumb {
111+
background: #4b5563;
112+
}
113+
114+
.overflow-auto::-webkit-scrollbar-thumb:hover {
115+
background: #6b7280;
116+
}
117+
}
118+
102119
/* Markdown content styling for chat messages */
103120
.markdown-content {
104121
line-height: 1.6;
@@ -170,6 +187,13 @@ html {
170187
border-radius: 3px;
171188
font-size: 85%;
172189
font-family: "Courier New", Consolas, Monaco, monospace;
190+
background-color: #f3f4f6;
191+
color: #1f2937;
192+
}
193+
194+
.dark .markdown-content code {
195+
background-color: #374151;
196+
color: #e5e7eb;
173197
}
174198

175199
/* Code blocks */
@@ -178,13 +202,19 @@ html {
178202
padding: 16px;
179203
overflow-x: auto;
180204
margin: 1em 0;
205+
background-color: #1f2937;
206+
}
207+
208+
.dark .markdown-content pre {
209+
background-color: #111827;
181210
}
182211

183212
.markdown-content pre code {
184213
background-color: transparent;
185214
padding: 0;
186215
font-size: 90%;
187216
line-height: 1.45;
217+
color: #e5e7eb;
188218
}
189219

190220
/* Tables */
@@ -194,23 +224,48 @@ html {
194224
margin: 1em 0;
195225
display: block;
196226
overflow-x: auto;
227+
background-color: transparent;
197228
}
198229

199230
.markdown-content table th,
200231
.markdown-content table td {
201-
border: 1px solid currentColor;
232+
border: 1px solid #d1d5db;
202233
padding: 8px 12px;
203234
text-align: left;
204-
opacity: 0.8;
235+
}
236+
237+
:where(.dark) .markdown-content table th,
238+
:where(.dark) .markdown-content table td {
239+
border-color: #4b5563;
205240
}
206241

207242
.markdown-content table th {
208243
font-weight: 600;
209-
opacity: 0.9;
244+
background-color: #f3f4f6;
245+
color: #374151;
246+
}
247+
248+
:where(.dark) .markdown-content table th {
249+
background-color: #374151 !important;
250+
color: #e5e7eb !important;
251+
}
252+
253+
.markdown-content table td {
254+
background-color: #ffffff;
255+
color: #374151;
256+
}
257+
258+
:where(.dark) .markdown-content table td {
259+
background-color: #1f2937 !important;
260+
color: #e5e7eb !important;
261+
}
262+
263+
.markdown-content table tr:nth-child(even) td {
264+
background-color: #f9fafb;
210265
}
211266

212-
.markdown-content table tr:nth-child(even) {
213-
opacity: 0.95;
267+
.dark .markdown-content table tr:nth-child(even) td {
268+
background-color: #111827;
214269
}
215270

216271
/* Links */

src/frontend/task-agent-web/app/layout.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { Metadata } from "next";
22
import { Geist, Geist_Mono } from "next/font/google";
3+
import { ThemeProvider } from "next-themes";
34
import "./globals.css";
45

56
const geistSans = Geist({
@@ -24,11 +25,13 @@ export default function RootLayout({
2425
children: React.ReactNode;
2526
}>) {
2627
return (
27-
<html lang="en">
28+
<html lang="en" suppressHydrationWarning>
2829
<body
2930
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
3031
>
31-
{children}
32+
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
33+
{children}
34+
</ThemeProvider>
3235
</body>
3336
</html>
3437
);

src/frontend/task-agent-web/components/chat/ChatHeader.tsx

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@
22

33
/**
44
* ChatHeader Component
5-
* Displays the chat header with sidebar toggle and title
5+
* Displays the chat header with sidebar toggle, title, and theme toggle
66
* Always visible in both desktop and mobile
77
*/
88

9+
import { ThemeToggle } from "@/components/shared/ThemeToggle";
10+
911
interface ChatHeaderProps {
1012
onToggleSidebar: () => void;
1113
isSidebarOpen?: boolean;
@@ -16,12 +18,12 @@ export function ChatHeader({
1618
isSidebarOpen,
1719
}: ChatHeaderProps) {
1820
return (
19-
<header className="flex-shrink-0 bg-white border-b border-gray-200 px-3 sm:px-4 py-3 sticky top-0 z-30">
21+
<header className="flex-shrink-0 bg-white dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700 px-3 sm:px-4 py-3 sticky top-0 z-30">
2022
<div className="flex items-center gap-2 sm:gap-3">
2123
{/* Sidebar toggle button - only visible on mobile */}
2224
<button
2325
onClick={onToggleSidebar}
24-
className="md:hidden p-2 rounded-lg hover:bg-gray-100 text-gray-700 transition-colors cursor-pointer"
26+
className="md:hidden p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-300 transition-colors cursor-pointer"
2527
aria-label={isSidebarOpen ? "Close sidebar" : "Open sidebar"}
2628
>
2729
<svg
@@ -42,15 +44,16 @@ export function ChatHeader({
4244
{/* Logo and Title */}
4345
<div className="flex items-center gap-2">
4446
<span className="text-xl sm:text-2xl">📋</span>
45-
<h1 className="font-semibold text-gray-900 text-base sm:text-lg">
47+
<h1 className="font-semibold text-gray-900 dark:text-white text-base sm:text-lg">
4648
Task Agent
4749
</h1>
4850
</div>
4951

5052
{/* Spacer */}
5153
<div className="flex-1"></div>
5254

53-
{/* Optional: Add more actions here like settings, user menu, etc */}
55+
{/* Theme Toggle */}
56+
<ThemeToggle />
5457
</div>
5558
</header>
5659
);

src/frontend/task-agent-web/components/chat/ErrorToast.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ export function ErrorToast({ error }: ErrorToastProps) {
1313
if (!error) return null;
1414

1515
return (
16-
<div className="fixed top-4 right-4 max-w-md bg-red-50 border-2 border-red-200 border-l-4 border-l-red-500 rounded-lg shadow-lg p-4 animate-fadeIn z-50">
16+
<div className="fixed top-4 right-4 max-w-md bg-red-50 dark:bg-red-900/30 border-2 border-red-200 dark:border-red-800 border-l-4 border-l-red-500 rounded-lg shadow-lg p-4 animate-fadeIn z-50">
1717
<div className="flex items-start gap-3">
18-
<div className="flex-shrink-0 text-red-600">
18+
<div className="flex-shrink-0 text-red-600 dark:text-red-400">
1919
<svg className="h-6 w-6" fill="currentColor" viewBox="0 0 20 20">
2020
<path
2121
fillRule="evenodd"
@@ -25,8 +25,8 @@ export function ErrorToast({ error }: ErrorToastProps) {
2525
</svg>
2626
</div>
2727
<div className="flex-1">
28-
<h3 className="text-sm font-semibold text-red-900 mb-1">⚠️ Error</h3>
29-
<p className="text-sm text-red-800">{error.message}</p>
28+
<h3 className="text-sm font-semibold text-red-900 dark:text-red-300 mb-1">⚠️ Error</h3>
29+
<p className="text-sm text-red-800 dark:text-red-400">{error.message}</p>
3030
</div>
3131
</div>
3232
</div>

src/frontend/task-agent-web/components/chat/SuggestionsBar.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,9 @@ export function SuggestionsBar({
3434
};
3535

3636
return (
37-
<div className="mt-3 pt-3 border-t border-gray-200">
37+
<div className="mt-3 pt-3 border-t border-gray-200 dark:border-gray-700">
3838
<div className="flex items-start gap-2 mb-2">
39-
<span className="text-xs font-semibold text-gray-600 flex items-center gap-1">
39+
<span className="text-xs font-semibold text-gray-600 dark:text-gray-400 flex items-center gap-1">
4040
<svg
4141
className="w-3.5 h-3.5 sm:w-4 sm:h-4"
4242
fill="none"
@@ -63,10 +63,15 @@ export function SuggestionsBar({
6363
group relative px-2.5 sm:px-3 py-1.5
6464
text-xs sm:text-sm font-medium
6565
bg-gradient-to-r from-blue-50 to-blue-100
66+
dark:from-blue-900/30 dark:to-blue-800/30
6667
hover:from-blue-100 hover:to-blue-200
68+
dark:hover:from-blue-800/40 dark:hover:to-blue-700/40
6769
active:from-blue-200 active:to-blue-300
70+
dark:active:from-blue-700/50 dark:active:to-blue-600/50
6871
text-blue-700 hover:text-blue-800
72+
dark:text-blue-400 dark:hover:text-blue-300
6973
border border-blue-200 hover:border-blue-300
74+
dark:border-blue-800 dark:hover:border-blue-700
7075
rounded-full
7176
transition-all duration-200
7277
cursor-pointer

src/frontend/task-agent-web/components/conversations/ConversationItem.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,8 @@ export function ConversationItem({
5050
touch-manipulation
5151
${
5252
isActive
53-
? "bg-blue-50 border border-blue-200"
54-
: "hover:bg-gray-100 active:bg-gray-200 border border-transparent"
53+
? "bg-blue-50 dark:bg-blue-900/30 border border-blue-200 dark:border-blue-800"
54+
: "hover:bg-gray-100 dark:hover:bg-gray-800 active:bg-gray-200 dark:active:bg-gray-700 border border-transparent"
5555
}
5656
`}
5757
>
@@ -66,14 +66,14 @@ export function ConversationItem({
6666
<h3
6767
className={`
6868
text-xs sm:text-sm font-medium truncate mb-1
69-
${isActive ? "text-blue-900" : "text-gray-900"}
69+
${isActive ? "text-blue-900 dark:text-blue-300" : "text-gray-900 dark:text-gray-100"}
7070
`}
7171
>
7272
{title}
7373
</h3>
7474

7575
{/* Metadata */}
76-
<div className="flex items-center gap-2 text-xs text-gray-500">
76+
<div className="flex items-center gap-2 text-xs text-gray-500 dark:text-gray-400">
7777
<span className="text-xs">
7878
{conversation.messageCount} messages
7979
</span>
@@ -87,7 +87,7 @@ export function ConversationItem({
8787
onClick={handleDelete}
8888
className="
8989
md:opacity-0 md:group-hover:opacity-100 transition-opacity
90-
p-1.5 sm:p-1 rounded hover:bg-red-50 active:bg-red-100 text-gray-400 hover:text-red-600
90+
p-1.5 sm:p-1 rounded hover:bg-red-50 dark:hover:bg-red-900/30 active:bg-red-100 dark:active:bg-red-900/50 text-gray-400 dark:text-gray-500 hover:text-red-600 dark:hover:text-red-400
9191
cursor-pointer
9292
touch-manipulation
9393
"

src/frontend/task-agent-web/components/conversations/ConversationList.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export function ConversationList({
2828
return (
2929
<div className="flex flex-col items-center justify-center py-12 px-4">
3030
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600 mb-3" />
31-
<p className="text-sm text-gray-500">Loading chats...</p>
31+
<p className="text-sm text-gray-500 dark:text-gray-400">Loading chats...</p>
3232
</div>
3333
);
3434
}
@@ -37,9 +37,9 @@ export function ConversationList({
3737
if (conversations.length === 0) {
3838
return (
3939
<div className="flex flex-col items-center justify-center py-12 px-4 text-center">
40-
<div className="w-12 h-12 rounded-full bg-gray-100 flex items-center justify-center mb-3">
40+
<div className="w-12 h-12 rounded-full bg-gray-100 dark:bg-gray-800 flex items-center justify-center mb-3">
4141
<svg
42-
className="w-6 h-6 text-gray-400"
42+
className="w-6 h-6 text-gray-400 dark:text-gray-500"
4343
fill="none"
4444
stroke="currentColor"
4545
viewBox="0 0 24 24"
@@ -52,10 +52,10 @@ export function ConversationList({
5252
/>
5353
</svg>
5454
</div>
55-
<p className="text-sm text-gray-600 font-medium mb-1">
55+
<p className="text-sm text-gray-600 dark:text-gray-400 font-medium mb-1">
5656
No chats yet
5757
</p>
58-
<p className="text-xs text-gray-500">
58+
<p className="text-xs text-gray-500 dark:text-gray-500">
5959
Start a new chat to get started
6060
</p>
6161
</div>

0 commit comments

Comments
 (0)