fix: dark mode flash if no cookie found (#779)

This commit is contained in:
Innei 2023-07-15 01:13:52 +08:00 committed by GitHub
parent cfcc6c562b
commit 745bee912a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 77 additions and 27 deletions

View File

@ -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
}

View File

@ -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}

View File

@ -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({

View File

@ -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"

View File

@ -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