diff --git a/src/app/ColorSchemeInjector.tsx b/src/app/ColorSchemeInjector.tsx new file mode 100644 index 00000000..cb8347fc --- /dev/null +++ b/src/app/ColorSchemeInjector.tsx @@ -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 ( + + ) + }) + return null +} diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 13658a54..9426e227 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -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 ( - + + {modal} diff --git a/src/hooks/useDarkMode.ts b/src/hooks/useDarkMode.ts index 1ce908f7..4541da84 100644 --- a/src/hooks/useDarkMode.ts +++ b/src/hooks/useDarkMode.ts @@ -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({ diff --git a/src/lib/constants.ts b/src/lib/constants.ts index 0db7c5f9..508e2a8f 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -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" diff --git a/src/lib/get-color-scheme.ts b/src/lib/get-color-scheme.ts index 8056322a..06873445 100644 --- a/src/lib/get-color-scheme.ts +++ b/src/lib/get-color-scheme.ts @@ -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