Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 | 16x 16x 16x 16x 16x 16x 5x 16x 16x 12x 12x 1x 32x 32x 32x 16x 3x 16x 43x 1x 14x 14x 14x 14x 14x 14x 15x 12x 12x 10x 10x 10x | import { useSyncExternalStore } from 'react'
import { TOKEN_KEY } from './auth'
import { API_BASE } from './useApi'
export type ThemeMode = 'LIGHT' | 'DARK' | 'AUTO'
export const THEME_MODES = ['LIGHT', 'AUTO', 'DARK'] as const satisfies readonly ThemeMode[]
const STORAGE_KEY = 'theme'
const darkQuery =
typeof window !== 'undefined' && window.matchMedia
? window.matchMedia('(prefers-color-scheme: dark)')
: null
function readStored(): ThemeMode {
try {
const stored = localStorage.getItem(STORAGE_KEY)
if (stored && (THEME_MODES as readonly string[]).includes(stored)) return stored as ThemeMode
} catch {
// fall back to AUTO
}
return 'AUTO'
}
let currentTheme: ThemeMode = readStored()
const listeners = new Set<() => void>()
function persistTheme(newTheme: ThemeMode): void {
const token = localStorage.getItem(TOKEN_KEY)
if (!token) return
fetch(`${API_BASE}/users/profile`, {
method: 'PUT',
headers: { 'content-type': 'application/json', authorization: `Bearer ${token}` },
body: JSON.stringify({ preferences: { theme: newTheme } }),
}).catch(() => {
})
}
function updateTheme(): void {
Iif (typeof document === 'undefined') return
const dark = currentTheme === 'DARK' || (currentTheme === 'AUTO' && (darkQuery?.matches ?? false))
document.documentElement.classList.toggle('dark', dark)
}
// while in AUTO, follow the OS theme as it changes
darkQuery?.addEventListener('change', () => {
if (currentTheme === 'AUTO') updateTheme()
})
updateTheme()
export function getThemeMode(): ThemeMode {
return currentTheme
}
export function systemPrefersDark(): boolean {
return darkQuery?.matches ?? false
}
export function applyTheme(newTheme: ThemeMode): void {
Iif (newTheme === currentTheme) return
currentTheme = newTheme
try {
localStorage.setItem(STORAGE_KEY, newTheme)
} catch {
// ignore persistence failures — the choice still applies for this session
}
updateTheme()
listeners.forEach((listener) => listener())
}
export function setThemeMode(newTheme: ThemeMode): void {
if (newTheme === currentTheme) return
applyTheme(newTheme)
persistTheme(newTheme)
}
export function useThemeMode(): ThemeMode {
return useSyncExternalStore(
(callback) => {
listeners.add(callback)
return () => listeners.delete(callback)
},
getThemeMode,
getThemeMode,
)
}
|