mirror of
https://github.com/Stone-Red-Code/website.git
synced 2026-09-04 09:15:58 +02:00
Merge pull request #419 from SagnikPradhan/feat/system-theme-preference
feat: dark/light mode
This commit is contained in:
+35
-9
@@ -2,11 +2,14 @@ import React, { createContext, FC, useMemo } from "react"
|
||||
import { ThemeProvider as BaseThemeProvider } from "styled-components"
|
||||
|
||||
import { darkTheme, lightTheme } from "./design/themes"
|
||||
import { useIsBrowser } from "./hooks/useIsBrowser"
|
||||
import { useLocalStorage } from "./hooks/useLocalStorage"
|
||||
import { useMedia } from "./hooks/useMedia"
|
||||
|
||||
type ThemeType = "dark" | "light"
|
||||
|
||||
export interface IThemeContext {
|
||||
theme: "dark" | "light"
|
||||
setTheme: () => void
|
||||
theme: ThemeType
|
||||
toggleTheme: () => void
|
||||
}
|
||||
|
||||
@@ -16,28 +19,51 @@ interface IScopedDownChildren {
|
||||
|
||||
export const ThemeContext = createContext<IThemeContext | null>(null)
|
||||
|
||||
const ThemeProvider: FC<IScopedDownChildren> = ({ children }) => {
|
||||
const [theme, setTheme] = useLocalStorage("theme", "dark")
|
||||
function useTheme() {
|
||||
const preferredTheme = useMedia<ThemeType>(
|
||||
["(prefers-color-scheme: light)", "(prefers-color-scheme: dark)"],
|
||||
["light", "dark"],
|
||||
"dark"
|
||||
)
|
||||
|
||||
const [userSelectedTheme, setUserSelectedTheme] = useLocalStorage<
|
||||
ThemeType | "unset"
|
||||
>("user-selected-theme", "unset")
|
||||
|
||||
const theme = useMemo(
|
||||
() => (userSelectedTheme !== "unset" ? userSelectedTheme : preferredTheme),
|
||||
[preferredTheme, userSelectedTheme]
|
||||
)
|
||||
|
||||
const themeObject = useMemo(
|
||||
() => (theme === "dark" ? darkTheme : lightTheme),
|
||||
[theme]
|
||||
)
|
||||
|
||||
return {
|
||||
theme,
|
||||
themeObject,
|
||||
setTheme: setUserSelectedTheme,
|
||||
}
|
||||
}
|
||||
|
||||
const ThemeProvider: FC<IScopedDownChildren> = ({ children }) => {
|
||||
const isBrowser = useIsBrowser()
|
||||
const { theme, themeObject, setTheme } = useTheme()
|
||||
|
||||
const contextValue = useMemo(
|
||||
() => ({
|
||||
theme,
|
||||
setTheme,
|
||||
toggleTheme: () => {
|
||||
setTheme(theme === "light" ? "dark" : "light")
|
||||
},
|
||||
toggleTheme: () => setTheme(theme === "light" ? "dark" : "light"),
|
||||
}),
|
||||
[theme, setTheme]
|
||||
)
|
||||
|
||||
return (
|
||||
<ThemeContext.Provider value={contextValue}>
|
||||
<BaseThemeProvider theme={themeObject}>{children}</BaseThemeProvider>
|
||||
<BaseThemeProvider theme={themeObject}>
|
||||
{isBrowser ? children : undefined}
|
||||
</BaseThemeProvider>
|
||||
</ThemeContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import { useEffect, useState } from "react"
|
||||
|
||||
export function useIsBrowser() {
|
||||
const isBrowserCheck = () => typeof window !== "undefined"
|
||||
|
||||
const [isBrowser, setIsBrowser] = useState(isBrowserCheck)
|
||||
|
||||
useEffect(() => setIsBrowser(isBrowserCheck), [])
|
||||
|
||||
return isBrowser
|
||||
}
|
||||
@@ -1,9 +1,13 @@
|
||||
/* globals window */
|
||||
import { useEffect, useState } from "react"
|
||||
|
||||
export const useLocalStorage = (name: string, initialValue: string) => {
|
||||
export const useLocalStorage = <Value extends unknown = unknown>(
|
||||
name: string,
|
||||
initialValue: Value
|
||||
) => {
|
||||
const windowGlobal = typeof window !== "undefined" && window
|
||||
const [value, setValue] = useState(() => {
|
||||
|
||||
const [value, setValue] = useState<Value>(() => {
|
||||
if (windowGlobal) {
|
||||
const currentValue = windowGlobal.localStorage.getItem(name)
|
||||
return currentValue ? JSON.parse(currentValue) : initialValue
|
||||
@@ -17,5 +21,5 @@ export const useLocalStorage = (name: string, initialValue: string) => {
|
||||
}
|
||||
}, [name, value, windowGlobal])
|
||||
|
||||
return [value, setValue]
|
||||
return [value, setValue] as const
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
/* globals window */
|
||||
import { useEffect, useState } from "react"
|
||||
|
||||
export function useMedia<Value extends unknown>(
|
||||
queries: string[],
|
||||
values: Value[],
|
||||
defaultValue: Value
|
||||
) {
|
||||
const [currentValue, setCurrentValue] = useState(defaultValue)
|
||||
|
||||
useEffect(() => {
|
||||
const windowGlobal = typeof window !== "undefined" && window
|
||||
|
||||
if (!windowGlobal) return
|
||||
|
||||
const mediaQueries = queries.map((query) => windowGlobal.matchMedia(query))
|
||||
|
||||
const getCurrentValue = () => {
|
||||
const index = mediaQueries.findIndex((value) => value.matches)
|
||||
return values[index] || defaultValue
|
||||
}
|
||||
|
||||
setCurrentValue(getCurrentValue)
|
||||
|
||||
const handler = () => setCurrentValue(getCurrentValue)
|
||||
|
||||
mediaQueries.forEach((query) => query.addEventListener("change", handler))
|
||||
|
||||
return () =>
|
||||
mediaQueries.forEach((query) =>
|
||||
query.removeEventListener("change", handler)
|
||||
)
|
||||
}, [queries, values, defaultValue])
|
||||
|
||||
return currentValue
|
||||
}
|
||||
Reference in New Issue
Block a user