fix: dark mode flash if no cookie found (#779)
This commit is contained in:
parent
cfcc6c562b
commit
745bee912a
|
|
@ -0,0 +1,42 @@
|
|||
"use client"
|
||||
|
||||
import { useServerInsertedHTML } from "next/navigation"
|
||||
|
||||
import {
|
||||
COLOR_SCHEME_DARK,
|
||||
COLOR_SCHEME_LIGHT,
|
||||
DARK_MODE_STORAGE_KEY,
|
||||
} from "~/lib/constants"
|
||||
import { getStorage } from "~/lib/storage"
|
||||
|
||||
export const ColorSchemeInjector = () => {
|
||||
useServerInsertedHTML(() => {
|
||||
return (
|
||||
<script
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `(() => {
|
||||
var DARK_MODE_STORAGE_KEY = "${DARK_MODE_STORAGE_KEY}";
|
||||
var getStorage = ${getStorage.toString()};
|
||||
var COLOR_SCHEME_LIGHT = "${COLOR_SCHEME_LIGHT}";
|
||||
var COLOR_SCHEME_DARK = "${COLOR_SCHEME_DARK}";
|
||||
|
||||
let data = {}
|
||||
const isDark = getStorage(DARK_MODE_STORAGE_KEY)
|
||||
if (typeof isDark === "undefined") {
|
||||
const currentColorScheme = window.matchMedia("(prefers-color-scheme: dark)")
|
||||
.matches
|
||||
? COLOR_SCHEME_DARK
|
||||
: COLOR_SCHEME_LIGHT
|
||||
document.documentElement.classList.add(currentColorScheme)
|
||||
} else {
|
||||
document.documentElement.classList.add(
|
||||
isDark ? COLOR_SCHEME_DARK : COLOR_SCHEME_LIGHT,
|
||||
)
|
||||
}
|
||||
})();`,
|
||||
}}
|
||||
></script>
|
||||
)
|
||||
})
|
||||
return null
|
||||
}
|
||||
|
|
@ -11,6 +11,7 @@ import { getAcceptLang } from "~/lib/accept-lang"
|
|||
import { APP_DESCRIPTION, APP_NAME, APP_SLOGAN, SITE_URL } from "~/lib/env"
|
||||
import { getColorScheme } from "~/lib/get-color-scheme"
|
||||
|
||||
import { ColorSchemeInjector } from "./ColorSchemeInjector"
|
||||
import Providers from "./providers"
|
||||
|
||||
export const metadata: Metadata = {
|
||||
|
|
@ -95,7 +96,13 @@ export default function RootLayout({
|
|||
}
|
||||
|
||||
return (
|
||||
<html lang={lang} dir={dir(lang)} className={colorScheme}>
|
||||
<html
|
||||
lang={lang}
|
||||
dir={dir(lang)}
|
||||
className={colorScheme}
|
||||
suppressHydrationWarning
|
||||
>
|
||||
<ColorSchemeInjector />
|
||||
<body>
|
||||
<Providers lang={lang}>
|
||||
{modal}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { create } from "zustand"
|
|||
import {
|
||||
COLOR_SCHEME_DARK,
|
||||
COLOR_SCHEME_LIGHT,
|
||||
DARK_MODE_STORAGE_KEY,
|
||||
DEFAULT_COLOR_SCHEME,
|
||||
IS_DEV,
|
||||
} from "~/lib/constants"
|
||||
|
|
@ -30,11 +31,9 @@ interface DarkModeConfig {
|
|||
classNameDark?: string // A className to set "dark mode". Default = "dark".
|
||||
classNameLight?: string // A className to set "light mode". Default = "light".
|
||||
element?: HTMLElement | undefined | null // The element to apply the className. Default = `document.body`.
|
||||
storageKey?: string // Specify the `localStorage` key. Default = "darkMode". set to `undefined` to disable persistent storage.
|
||||
transition?: ViewTransition | undefined // Specify the `animate` when switching the mode. Only Chromium >= 111 etc.
|
||||
}
|
||||
|
||||
const darkModeKey = "darkMode"
|
||||
const useDarkModeInternal = (
|
||||
initialState: boolean | undefined,
|
||||
options: DarkModeConfig,
|
||||
|
|
@ -42,7 +41,6 @@ const useDarkModeInternal = (
|
|||
const {
|
||||
classNameDark = COLOR_SCHEME_DARK,
|
||||
classNameLight = COLOR_SCHEME_LIGHT,
|
||||
storageKey = darkModeKey,
|
||||
element,
|
||||
transition,
|
||||
} = options
|
||||
|
|
@ -51,29 +49,30 @@ const useDarkModeInternal = (
|
|||
const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 })
|
||||
|
||||
useEffect(() => {
|
||||
const presentedDarkMode = !isServerSide() && getStorage(storageKey)
|
||||
const presentedDarkMode =
|
||||
!isServerSide() && getStorage(DARK_MODE_STORAGE_KEY)
|
||||
|
||||
if (presentedDarkMode !== undefined) {
|
||||
setDarkMode(presentedDarkMode === "true")
|
||||
} else if (typeof initialState === "undefined") {
|
||||
setDarkMode(window.matchMedia("(prefers-color-scheme: dark)").matches)
|
||||
}
|
||||
}, [storageKey])
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
const handler = (e: MediaQueryListEvent) => {
|
||||
const storageValue = getStorage(storageKey)
|
||||
const storageValue = getStorage(DARK_MODE_STORAGE_KEY)
|
||||
const parseStorageValueAsBool = storageValue === "true"
|
||||
setDarkMode(e.matches)
|
||||
|
||||
// reset dark mode, follow system
|
||||
if (parseStorageValueAsBool === e.matches) {
|
||||
delStorage(storageKey)
|
||||
delStorage(DARK_MODE_STORAGE_KEY)
|
||||
}
|
||||
}
|
||||
|
||||
const storageHandler = () => {
|
||||
const storageValue = getStorage(storageKey, true)
|
||||
const storageValue = getStorage(DARK_MODE_STORAGE_KEY, true)
|
||||
// if not storage color mode, switch to follow system
|
||||
if (storageValue === undefined) {
|
||||
setDarkMode(window.matchMedia("(prefers-color-scheme: dark)").matches)
|
||||
|
|
@ -94,7 +93,7 @@ const useDarkModeInternal = (
|
|||
.matchMedia("(prefers-color-scheme: dark)")
|
||||
.removeEventListener("change", handler)
|
||||
}
|
||||
}, [storageKey])
|
||||
}, [])
|
||||
|
||||
const getDarkMode = useGetState(darkMode)
|
||||
useEffect(() => {
|
||||
|
|
@ -104,7 +103,7 @@ const useDarkModeInternal = (
|
|||
window.matchMedia("(prefers-color-scheme: dark)").matches ===
|
||||
getDarkMode()
|
||||
) {
|
||||
delStorage(darkModeKey)
|
||||
delStorage(DARK_MODE_STORAGE_KEY)
|
||||
}
|
||||
}
|
||||
window.addEventListener("beforeunload", handler)
|
||||
|
|
@ -166,8 +165,8 @@ const useDarkModeInternal = (
|
|||
value: darkMode,
|
||||
toggle: (e: MouseEvent) => {
|
||||
setDarkMode((d) => {
|
||||
if (storageKey && !isServerSide()) {
|
||||
setStorage(storageKey, String(!d))
|
||||
if (DARK_MODE_STORAGE_KEY && !isServerSide()) {
|
||||
setStorage(DARK_MODE_STORAGE_KEY, String(!d))
|
||||
setMousePosition({ x: e.clientX, y: e.clientY })
|
||||
}
|
||||
return !d
|
||||
|
|
@ -184,18 +183,20 @@ const mockElement = {
|
|||
}
|
||||
|
||||
export const useDarkMode = () => {
|
||||
const { toggle, value } = useDarkModeInternal(getStorage(darkModeKey), {
|
||||
classNameDark: COLOR_SCHEME_DARK,
|
||||
classNameLight: COLOR_SCHEME_LIGHT,
|
||||
storageKey: darkModeKey,
|
||||
element: (globalThis.document && document.documentElement) || mockElement,
|
||||
transition:
|
||||
!isServerSide() &&
|
||||
!!document.startViewTransition &&
|
||||
!window.matchMedia(`(prefers-reduced-motion: reduce)`).matches
|
||||
? document.startViewTransition()
|
||||
: undefined,
|
||||
})
|
||||
const { toggle, value } = useDarkModeInternal(
|
||||
getStorage(DARK_MODE_STORAGE_KEY),
|
||||
{
|
||||
classNameDark: COLOR_SCHEME_DARK,
|
||||
classNameLight: COLOR_SCHEME_LIGHT,
|
||||
element: (globalThis.document && document.documentElement) || mockElement,
|
||||
transition:
|
||||
!isServerSide() &&
|
||||
!!document.startViewTransition &&
|
||||
!window.matchMedia(`(prefers-reduced-motion: reduce)`).matches
|
||||
? document.startViewTransition()
|
||||
: undefined,
|
||||
},
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
useMediaStore.setState({
|
||||
|
|
|
|||
|
|
@ -16,3 +16,4 @@ export const MAXIMUM_FILE_SIZE = 100 // MB
|
|||
export const COLOR_SCHEME_DARK = "dark"
|
||||
export const COLOR_SCHEME_LIGHT = "light"
|
||||
export const DEFAULT_COLOR_SCHEME: ColorScheme = "light"
|
||||
export const DARK_MODE_STORAGE_KEY = "darkMode"
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import { cookies } from "next/headers"
|
||||
|
||||
import { DEFAULT_COLOR_SCHEME } from "./constants"
|
||||
import { ColorScheme } from "./types"
|
||||
|
||||
export const getColorScheme = () =>
|
||||
(cookies().get("color_scheme")?.value || DEFAULT_COLOR_SCHEME) as ColorScheme
|
||||
cookies().get("color_scheme")?.value as ColorScheme
|
||||
|
|
|
|||
Loading…
Reference in New Issue