From a4c4ac9e51f7f622a8ee6adb912b96b263345f07 Mon Sep 17 00:00:00 2001 From: antopoid Date: Sun, 22 Feb 2026 20:00:54 +0100 Subject: [PATCH] feat: dark mode with theme toggle (light/dark/system) --- src/hooks/useTheme.ts | 57 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 src/hooks/useTheme.ts diff --git a/src/hooks/useTheme.ts b/src/hooks/useTheme.ts new file mode 100644 index 0000000..ee4264b --- /dev/null +++ b/src/hooks/useTheme.ts @@ -0,0 +1,57 @@ +'use client'; + +import { useState, useEffect, useCallback } from 'react'; + +type Theme = 'light' | 'dark' | 'system'; + +function getSystemTheme(): 'light' | 'dark' { + if (typeof window === 'undefined') return 'light'; + return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; +} + +function applyTheme(theme: Theme) { + const resolved = theme === 'system' ? getSystemTheme() : theme; + const root = document.documentElement; + if (resolved === 'dark') { + root.classList.add('dark'); + } else { + root.classList.remove('dark'); + } +} + +export function useTheme() { + const [theme, setThemeState] = useState('system'); + + // Read from localStorage on mount + useEffect(() => { + const stored = localStorage.getItem('simo-theme') as Theme | null; + const initial = stored ?? 'system'; + setThemeState(initial); + applyTheme(initial); + }, []); + + // Listen for system theme changes when in 'system' mode + useEffect(() => { + const mq = window.matchMedia('(prefers-color-scheme: dark)'); + const handler = () => { + if (theme === 'system') applyTheme('system'); + }; + mq.addEventListener('change', handler); + return () => mq.removeEventListener('change', handler); + }, [theme]); + + const setTheme = useCallback((t: Theme) => { + setThemeState(t); + localStorage.setItem('simo-theme', t); + applyTheme(t); + }, []); + + const cycle = useCallback(() => { + const order: Theme[] = ['light', 'dark', 'system']; + const idx = order.indexOf(theme); + const next = order[(idx + 1) % order.length]; + setTheme(next); + }, [theme, setTheme]); + + return { theme, setTheme, cycle }; +}