88 * ────────────────────────────────────────────── */
99
1010import React , { useEffect , useRef , useState } from "react" ;
11- import { Copy , Check , Share2 , Trash2 , Search , Inbox , Download , Upload , AlertCircle } from "lucide-react" ;
11+ import { Copy , Check , Share2 , Trash2 , Search , Inbox , Download , Upload , AlertCircle , Moon , Sun } from "lucide-react" ;
1212import { HacklmLogo } from "../components/HacklmIcon" ;
1313import MarkdownContent from "../components/MarkdownContent" ;
1414import { stripMarkdown } from "../utils/stripMarkdown" ;
@@ -67,6 +67,15 @@ export default function App() {
6767 const fileInputRef = useRef < HTMLInputElement > ( null ) ;
6868 const [ copyMode , setCopyMode ] = useState < "md" | "txt" > ( "md" ) ;
6969 const [ copiedId , setCopiedId ] = useState < string | null > ( null ) ;
70+ const [ dark , setDark ] = useState < boolean > ( ( ) => {
71+ return localStorage . getItem ( "theme" ) === "dark" ;
72+ } ) ;
73+
74+ const toggleDark = ( ) => {
75+ const next = ! dark ;
76+ setDark ( next ) ;
77+ localStorage . setItem ( "theme" , next ? "dark" : "light" ) ;
78+ } ;
7079
7180 const copyMessage = async ( id : string , content : string ) => {
7281 const text = copyMode === "md" ? content : stripMarkdown ( content ) ;
@@ -181,19 +190,27 @@ export default function App() {
181190 } ;
182191
183192 return (
184- < div className = " flex h-screen bg-background" >
193+ < div className = { ` flex h-screen bg-background${ dark ? " dark" : "" } ` } >
185194 { /* ── Sidebar ──────────────────────────────────────── */ }
186- < aside className = "w-64 shrink-0 bg-white border-r border-grid p-4 flex flex-col gap-4" >
187- < div className = "pb-4 border-b border-grid" >
195+ < aside className = "w-64 shrink-0 bg-white dark:bg-[#1a1a1a] border-r border-grid p-4 flex flex-col gap-4" >
196+ < div className = "pb-4 border-b border-grid flex items-center justify-between " >
188197 < HacklmLogo size = { 28 } />
198+ < button
199+ onClick = { toggleDark }
200+ title = { dark ? "Switch to light mode" : "Switch to dark mode" }
201+ className = "p-1.5 rounded-sm text-faint hover:text-muted hover:bg-gray-100
202+ dark:hover:bg-[#262626] transition"
203+ >
204+ { dark ? < Sun size = { 14 } strokeWidth = { 2 } /> : < Moon size = { 14 } strokeWidth = { 2 } /> }
205+ </ button >
189206 </ div >
190207
191208 < div className = "flex flex-col gap-1 text-sm font-mono mt-2" >
192209 < button
193210 onClick = { ( ) => setFilter ( "all" ) }
194211 className = { `text-left px-3 py-2 rounded-sm transition ${ filter === "all"
195- ? "bg-gray-100 text-black border border-grid"
196- : "text-muted hover:bg-gray-50 border border-transparent"
212+ ? "bg-gray-100 dark:bg-[#262626] text-black dark:text-[#e8e8e8] border border-grid"
213+ : "text-muted hover:bg-gray-50 dark:hover:bg-[#1f1f1f] border border-transparent"
197214 } `}
198215 >
199216 All ({ index . length } )
@@ -205,8 +222,8 @@ export default function App() {
205222 key = { p }
206223 onClick = { ( ) => setFilter ( p ) }
207224 className = { `text-left px-3 py-2 rounded-sm transition flex items-center gap-2 ${ filter === p
208- ? "bg-gray-100 text-black border border-grid"
209- : "text-muted hover:bg-gray-50 border border-transparent"
225+ ? "bg-gray-100 dark:bg-[#262626] text-black dark:text-[#e8e8e8] border border-grid"
226+ : "text-muted hover:bg-gray-50 dark:hover:bg-[#1f1f1f] border border-transparent"
210227 } `}
211228 >
212229 < span
@@ -226,7 +243,7 @@ export default function App() {
226243 { ( ( ) => {
227244 if ( ! lastExported ) {
228245 return (
229- < div className = "flex items-start gap-2 text-xs font-mono text-amber-700 bg-amber-50 border border-amber-200 rounded-sm px-3 py-2" >
246+ < div className = "flex items-start gap-2 text-xs font-mono text-amber-700 dark:text-amber-400 bg-amber-50 dark:bg-amber-950/40 border border-amber-200 dark:border-amber-800 rounded-sm px-3 py-2" >
230247 < AlertCircle size = { 12 } className = "mt-0.5 shrink-0" />
231248 < span > No backup yet. Export your data before uninstalling.</ span >
232249 </ div >
@@ -237,7 +254,7 @@ export default function App() {
237254 ) ;
238255 if ( daysSince > 7 ) {
239256 return (
240- < div className = "flex items-start gap-2 text-xs font-mono text-amber-700 bg-amber-50 border border-amber-200 rounded-sm px-3 py-2" >
257+ < div className = "flex items-start gap-2 text-xs font-mono text-amber-700 dark:text-amber-400 bg-amber-50 dark:bg-amber-950/40 border border-amber-200 dark:border-amber-800 rounded-sm px-3 py-2" >
241258 < AlertCircle size = { 12 } className = "mt-0.5 shrink-0" />
242259 < span > Last backup { daysSince } d ago. Consider exporting.</ span >
243260 </ div >
@@ -277,8 +294,9 @@ export default function App() {
277294 fileInputRef . current ?. click ( ) ;
278295 } }
279296 disabled = { importing }
280- className = "w-full flex items-center justify-center gap-2 px-3 py-2 bg-white border border-grid
281- hover:bg-gray-50 text-black text-xs font-mono rounded-sm transition disabled:opacity-50"
297+ className = "w-full flex items-center justify-center gap-2 px-3 py-2 bg-white dark:bg-[#1a1a1a]
298+ border border-grid hover:bg-gray-50 dark:hover:bg-[#1f1f1f]
299+ text-black dark:text-[#e8e8e8] text-xs font-mono rounded-sm transition disabled:opacity-50"
282300 >
283301 < Upload size = { 12 } strokeWidth = { 2 } />
284302 { importing ? "Importing…" : "Import File" }
@@ -288,8 +306,8 @@ export default function App() {
288306 < div
289307 className = { `text-[11px] font-mono px-2 py-1.5 rounded-sm border ${
290308 importStatus . error
291- ? "text-red-700 bg-red-50 border-red-200"
292- : "text-green-700 bg-green-50 border-green-200"
309+ ? "text-red-700 dark:text-red-400 bg-red-50 dark:bg-red-950/40 border-red-200 dark:border-red-800 "
310+ : "text-green-700 dark:text-green-400 bg-green-50 dark:bg-green-950/40 border-green-200 dark:border-green-800 "
293311 } `}
294312 >
295313 { importStatus . error
@@ -304,7 +322,7 @@ export default function App() {
304322
305323 { /* ── Thread list ──────────────────────────────────── */ }
306324 < section className = "w-80 shrink-0 border-r border-grid bg-background flex flex-col" >
307- < div className = "p-3 border-b border-grid bg-white" >
325+ < div className = "p-3 border-b border-grid bg-white dark:bg-[#1a1a1a] " >
308326 < div className = "relative" >
309327 < Search size = { 14 } className = "absolute left-3 top-1/2 -translate-y-1/2 text-faint pointer-events-none" />
310328 < input
@@ -335,8 +353,8 @@ export default function App() {
335353 onClick = { ( ) => openThread ( t . id ) }
336354 className = { `w-full text-left px-4 py-3 border-b border-grid transition
337355 ${ active ?. id === t . id
338- ? "bg-gray-100"
339- : "bg-white hover:bg-gray-50"
356+ ? "bg-gray-100 dark:bg-[#262626] "
357+ : "bg-white dark:bg-[#1a1a1a] hover:bg-gray-50 dark:hover:bg-[#1f1f1f] "
340358 } `}
341359 >
342360 < div className = "font-medium text-sm truncate" > { t . title } </ div >
@@ -353,7 +371,7 @@ export default function App() {
353371 </ section >
354372
355373 { /* ── Chat viewer ──────────────────────────────────── */ }
356- < main className = "flex-1 flex flex-col min-w-0 bg-white" >
374+ < main className = "flex-1 flex flex-col min-w-0 bg-white dark:bg-[#111111] " >
357375 { ! active ? (
358376 < div className = "flex-1 flex items-center justify-center text-muted" >
359377 < div className = "text-center" >
@@ -386,8 +404,8 @@ export default function App() {
386404 </ button >
387405 < button
388406 onClick = { ( ) => deleteThread ( active . id ) }
389- className = "px-4 py-2 bg-white border border-grid hover:bg-gray-50 text-black text-xs font-mono
390- rounded-sm transition tracking-tight flex items-center gap-1.5"
407+ className = "px-4 py-2 bg-white dark:bg-[#1a1a1a] border border-grid hover:bg-gray-50 dark:hover:bg-[#1f1f1f]
408+ text-black dark:text-[#e8e8e8] text-xs font-mono rounded-sm transition tracking-tight flex items-center gap-1.5"
391409 >
392410 < Trash2 size = { 12 } strokeWidth = { 2 } /> Delete
393411 </ button >
@@ -402,7 +420,7 @@ export default function App() {
402420 className = { `px-2 py-0.5 text-[10px] font-mono rounded-sm border transition
403421 ${ copyMode === "md"
404422 ? "border-accent text-accent bg-accent/5"
405- : "border-grid text-faint hover:border-muted hover:text-muted" } `}
423+ : "border-grid text-faint hover:border-muted hover:text-muted dark:hover:text-[#888] " } `}
406424 >
407425 MD
408426 </ button >
@@ -411,7 +429,7 @@ export default function App() {
411429 className = { `px-2 py-0.5 text-[10px] font-mono rounded-sm border transition
412430 ${ copyMode === "txt"
413431 ? "border-accent text-accent bg-accent/5"
414- : "border-grid text-faint hover:border-muted hover:text-muted" } `}
432+ : "border-grid text-faint hover:border-muted hover:text-muted dark:hover:text-[#888] " } `}
415433 >
416434 TXT
417435 </ button >
@@ -433,7 +451,8 @@ export default function App() {
433451 onClick = { ( ) => copyMessage ( m . id , m . content ) }
434452 title = { `Copy as ${ copyMode === "md" ? "markdown" : "plain text" } ` }
435453 className = "flex items-center gap-1 px-2 py-1 text-[10px] font-mono rounded-sm
436- bg-white border border-grid text-muted hover:text-black hover:border-muted transition"
454+ bg-white dark:bg-[#1a1a1a] border border-grid text-muted
455+ hover:text-black dark:hover:text-[#e8e8e8] hover:border-muted transition"
437456 >
438457 { copiedId === m . id
439458 ? < > < Check size = { 10 } strokeWidth = { 2.5 } /> Copied</ >
@@ -443,7 +462,8 @@ export default function App() {
443462 onClick = { ( ) => shareMessage ( m . content ) }
444463 title = "Share message"
445464 className = "flex items-center gap-1 px-2 py-1 text-[10px] font-mono rounded-sm
446- bg-white border border-grid text-muted hover:text-black hover:border-muted transition"
465+ bg-white dark:bg-[#1a1a1a] border border-grid text-muted
466+ hover:text-black dark:hover:text-[#e8e8e8] hover:border-muted transition"
447467 >
448468 < Share2 size = { 10 } strokeWidth = { 2 } /> Share
449469 </ button >
@@ -452,8 +472,8 @@ export default function App() {
452472 < div
453473 className = { `px-5 py-4 rounded-sm text-sm leading-relaxed border border-grid overflow-hidden
454474 ${ m . role === "user"
455- ? "bg-gray-50 text-black"
456- : "bg-white text-black" } `}
475+ ? "bg-gray-50 dark:bg-[#1f1f1f] text-black dark:text-[#e8e8e8] "
476+ : "bg-white dark:bg-[#1a1a1a] text-black dark:text-[#e8e8e8] " } `}
457477 >
458478 < MarkdownContent > { m . content } </ MarkdownContent >
459479 </ div >
0 commit comments