76 lines
2.0 KiB
JavaScript
76 lines
2.0 KiB
JavaScript
import { useEffect, useState } from 'react'
|
|
|
|
const handleThemeChange = (dark) => {
|
|
const root = window.document.documentElement
|
|
root.setAttribute('data-bs-theme', dark ? 'dark' : 'light')
|
|
}
|
|
|
|
const STORAGE_KEY = 'darkMode'
|
|
const PREFER_DARK_QUERY = '(prefers-color-scheme: dark)'
|
|
|
|
const getTheme = () => {
|
|
const mql = window.matchMedia(PREFER_DARK_QUERY)
|
|
const supportsColorSchemeQuery = mql.media === PREFER_DARK_QUERY
|
|
let localStorageTheme = null
|
|
try {
|
|
localStorageTheme = window.localStorage.getItem(STORAGE_KEY)
|
|
} catch (err) {}
|
|
const localStorageExists = localStorageTheme !== null
|
|
if (localStorageExists) {
|
|
localStorageTheme = JSON.parse(localStorageTheme)
|
|
}
|
|
|
|
if (localStorageExists) {
|
|
return { user: true, dark: localStorageTheme }
|
|
} else if (supportsColorSchemeQuery) {
|
|
return { user: false, dark: mql.matches }
|
|
}
|
|
}
|
|
|
|
const setTheme = (dark) => {
|
|
window.localStorage.setItem(STORAGE_KEY, JSON.stringify(dark))
|
|
handleThemeChange(dark)
|
|
}
|
|
|
|
const listenForThemeChange = (onChange) => {
|
|
const mql = window.matchMedia(PREFER_DARK_QUERY)
|
|
mql.onchange = mql => {
|
|
const { user, dark } = getTheme()
|
|
if (!user) {
|
|
handleThemeChange(dark)
|
|
onChange({ user, dark })
|
|
}
|
|
}
|
|
window.onstorage = e => {
|
|
if (e.key === STORAGE_KEY) {
|
|
const dark = JSON.parse(e.newValue)
|
|
setTheme(dark)
|
|
onChange({ user: true, dark })
|
|
}
|
|
}
|
|
|
|
const root = window.document.documentElement
|
|
const observer = new window.MutationObserver(() => {
|
|
const theme = root.getAttribute('data-bs-theme')
|
|
onChange(dark => ({ ...dark, dark: theme === 'dark' }))
|
|
})
|
|
observer.observe(root, { attributes: true, attributeFilter: ['data-bs-theme'] })
|
|
|
|
return () => observer.disconnect()
|
|
}
|
|
|
|
export default function useDarkMode () {
|
|
const [dark, setDark] = useState()
|
|
|
|
useEffect(() => {
|
|
const { user, dark } = getTheme()
|
|
setDark({ user, dark })
|
|
listenForThemeChange(setDark)
|
|
}, [])
|
|
|
|
return [dark?.dark, () => {
|
|
setTheme(!dark.dark)
|
|
setDark({ user: true, dark: !dark.dark })
|
|
}]
|
|
}
|