feat: add dark/light theme toggle with localStorage persistence
This commit is contained in:
parent
ace036b1fc
commit
2a4d66faa9
@ -11,7 +11,8 @@ import { CreateTimerModal } from './CreateTimerModal';
|
|||||||
import { AlarmOverlay } from './AlarmOverlay';
|
import { AlarmOverlay } from './AlarmOverlay';
|
||||||
import { requestNotificationPermission } from '@/lib/notifications';
|
import { requestNotificationPermission } from '@/lib/notifications';
|
||||||
import { formatTime, formatDate } from '@/lib/format';
|
import { formatTime, formatDate } from '@/lib/format';
|
||||||
import { Plus, Clock, Bell, Keyboard } from 'lucide-react';
|
import { Plus, Clock, Bell, Keyboard, Sun, Moon } from 'lucide-react';
|
||||||
|
import { useTheme } from '@/lib/use-theme';
|
||||||
|
|
||||||
export function Dashboard() {
|
export function Dashboard() {
|
||||||
const [isCreateOpen, setIsCreateOpen] = useState(false);
|
const [isCreateOpen, setIsCreateOpen] = useState(false);
|
||||||
@ -20,6 +21,7 @@ export function Dashboard() {
|
|||||||
const timers = useTimerStore((s) => s.timers);
|
const timers = useTimerStore((s) => s.timers);
|
||||||
const now = useTimerStore((s) => s.now);
|
const now = useTimerStore((s) => s.now);
|
||||||
const { pause, resume } = useTimerStore();
|
const { pause, resume } = useTimerStore();
|
||||||
|
const { theme, toggle: toggleTheme } = useTheme();
|
||||||
|
|
||||||
// Start the tick loop
|
// Start the tick loop
|
||||||
useTickLoop();
|
useTickLoop();
|
||||||
@ -110,6 +112,14 @@ export function Dashboard() {
|
|||||||
<span className="text-sm font-mono" style={{ color: 'var(--cm-text-tertiary)' }}>
|
<span className="text-sm font-mono" style={{ color: 'var(--cm-text-tertiary)' }}>
|
||||||
{formatTime(now)} · {formatDate(now)}
|
{formatTime(now)} · {formatDate(now)}
|
||||||
</span>
|
</span>
|
||||||
|
<button
|
||||||
|
onClick={toggleTheme}
|
||||||
|
className="p-2 rounded-lg transition-colors cursor-pointer"
|
||||||
|
style={{ color: 'var(--cm-text-tertiary)' }}
|
||||||
|
title={`Switch to ${theme === 'dark' ? 'light' : 'dark'} mode`}
|
||||||
|
>
|
||||||
|
{theme === 'dark' ? <Sun size={18} /> : <Moon size={18} />}
|
||||||
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => setShowShortcuts((p) => !p)}
|
onClick={() => setShowShortcuts((p) => !p)}
|
||||||
className="p-2 rounded-lg transition-colors cursor-pointer"
|
className="p-2 rounded-lg transition-colors cursor-pointer"
|
||||||
|
|||||||
35
web/src/lib/use-theme.ts
Normal file
35
web/src/lib/use-theme.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
// ── Dark/Light Theme Toggle ────────────────────────────────────
|
||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
export type Theme = 'dark' | 'light';
|
||||||
|
|
||||||
|
export function useTheme() {
|
||||||
|
const [theme, setTheme] = useState<Theme>('dark');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const stored = localStorage.getItem('chronomind-theme') as Theme | null;
|
||||||
|
if (stored) {
|
||||||
|
setTheme(stored);
|
||||||
|
applyTheme(stored);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const toggle = () => {
|
||||||
|
const next = theme === 'dark' ? 'light' : 'dark';
|
||||||
|
setTheme(next);
|
||||||
|
applyTheme(next);
|
||||||
|
localStorage.setItem('chronomind-theme', next);
|
||||||
|
};
|
||||||
|
|
||||||
|
return { theme, toggle };
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyTheme(theme: Theme) {
|
||||||
|
if (typeof document === 'undefined') return;
|
||||||
|
document.documentElement.classList.remove('light');
|
||||||
|
if (theme === 'light') {
|
||||||
|
document.documentElement.classList.add('light');
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user