mirror of
				https://github.com/jakejarvis/jarv.is.git
				synced 2025-10-31 06:26:03 -04:00 
			
		
		
		
	consolidate theme context/provider types
This commit is contained in:
		| @@ -1,8 +1,6 @@ | |||||||
| import { useRouter } from "next/router"; | import { useRouter } from "next/router"; | ||||||
| import NextLink from "next/link"; | import NextLink from "next/link"; | ||||||
| import urlJoin from "url-join"; |  | ||||||
| import { styled } from "../../lib/styles/stitches.config"; | import { styled } from "../../lib/styles/stitches.config"; | ||||||
| import { baseUrl } from "../../lib/config"; |  | ||||||
| import type { ComponentProps } from "react"; | import type { ComponentProps } from "react"; | ||||||
|  |  | ||||||
| const Title = styled("h1", { | const Title = styled("h1", { | ||||||
| @@ -25,11 +23,10 @@ export type PageTitleProps = ComponentProps<typeof Title>; | |||||||
|  |  | ||||||
| const PageTitle = ({ children, ...rest }: PageTitleProps) => { | const PageTitle = ({ children, ...rest }: PageTitleProps) => { | ||||||
|   const router = useRouter(); |   const router = useRouter(); | ||||||
|   const canonical = urlJoin(baseUrl, router.pathname, "/"); |  | ||||||
|  |  | ||||||
|   return ( |   return ( | ||||||
|     <Title {...rest}> |     <Title {...rest}> | ||||||
|       <NextLink href={canonical} passHref={true}> |       <NextLink href={router.pathname} passHref={true}> | ||||||
|         <Link>{children}</Link> |         <Link>{children}</Link> | ||||||
|       </NextLink> |       </NextLink> | ||||||
|     </Title> |     </Title> | ||||||
|   | |||||||
| @@ -2,53 +2,57 @@ | |||||||
| // https://github.com/pacocoursey/next-themes/blob/b5c2bad50de2d61ad7b52a9c5cdc801a78507d7a/index.tsx | // https://github.com/pacocoursey/next-themes/blob/b5c2bad50de2d61ad7b52a9c5cdc801a78507d7a/index.tsx | ||||||
|  |  | ||||||
| import { createContext, useCallback, useEffect, useState, useRef } from "react"; | import { createContext, useCallback, useEffect, useState, useRef } from "react"; | ||||||
| import { darkModeQuery, colorSchemes, themeStorageKey } from "../lib/styles/helpers/themes"; | import { darkModeQuery, themeStorageKey } from "../lib/styles/helpers/themes"; | ||||||
| import type { PropsWithChildren } from "react"; | import type { Context, PropsWithChildren } from "react"; | ||||||
| import type { UseThemeProps } from "../hooks/use-theme"; |  | ||||||
|  |  | ||||||
| export interface ThemeProviderProps { | export const ThemeContext: Context<{ | ||||||
|   /** Mapping of theme name to HTML attribute value. Object where key is the theme name and value is the attribute value */ |   /** Update the theme */ | ||||||
|   classNames: { [themeName: string]: string }; |   setTheme?: (theme: string) => void; | ||||||
|   /** List of all available theme names */ |   /** List of all available theme names (probably "light", "dark", and "system") */ | ||||||
|   themes?: string[]; |   themes?: string[]; | ||||||
|   /** Whether to indicate to browsers which color scheme is used (dark or light) for built-in UI like inputs and buttons */ |   /** Active theme name ("system" if unset) */ | ||||||
|   enableColorScheme?: boolean; |   theme?: string; | ||||||
| } |   /** If the active theme is "system", this returns whether the system preference resolved to "dark" or "light". Otherwise, identical to `theme` */ | ||||||
|  |   resolvedTheme?: string; | ||||||
| // get the user's saved theme preference | }> = createContext({}); | ||||||
| const getUserTheme = (key: string, fallback?: string) => { |  | ||||||
|   if (typeof window === "undefined") return undefined; |  | ||||||
|  |  | ||||||
|   let theme: string; |  | ||||||
|   try { |  | ||||||
|     theme = localStorage.getItem(key) || undefined; |  | ||||||
|   } catch (e) {} // eslint-disable-line no-empty |  | ||||||
|  |  | ||||||
|   return theme || fallback; |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| // get the user's preferred theme via their OS/browser settings |  | ||||||
| const getSystemTheme = (e?: MediaQueryList) => { |  | ||||||
|   if (!e) { |  | ||||||
|     e = window.matchMedia(darkModeQuery); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   return e.matches ? "dark" : "light"; |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| export const ThemeContext = createContext<UseThemeProps>({ |  | ||||||
|   // eslint-disable-next-line @typescript-eslint/no-empty-function, @typescript-eslint/no-unused-vars |  | ||||||
|   setTheme: (_) => {}, |  | ||||||
|   themes: [], |  | ||||||
| }); |  | ||||||
|  |  | ||||||
| // provider used once in _app.tsx to wrap entire app | // provider used once in _app.tsx to wrap entire app | ||||||
| export const ThemeProvider = ({ | export const ThemeProvider = ({ | ||||||
|   classNames, |   classNames, | ||||||
|   themes = [...colorSchemes], |   enableColorScheme, | ||||||
|   enableColorScheme = true, |  | ||||||
|   children, |   children, | ||||||
| }: PropsWithChildren<ThemeProviderProps>) => { | }: PropsWithChildren<{ | ||||||
|  |   /** Mapping of theme name to HTML attribute value. Object where key is the theme name and value is the attribute value */ | ||||||
|  |   classNames: { | ||||||
|  |     [themeName: string]: string; | ||||||
|  |   }; | ||||||
|  |   /** Whether to indicate to browsers which color scheme is used (dark or light) for built-in UI like inputs and buttons */ | ||||||
|  |   enableColorScheme?: boolean; | ||||||
|  | }>) => { | ||||||
|  |   // possible themes are derived from given classNames (probably "light" and "dark") | ||||||
|  |   const themes = Object.keys(classNames); | ||||||
|  |  | ||||||
|  |   // get the user's saved theme preference | ||||||
|  |   const getUserTheme = (key: string, fallback?: string) => { | ||||||
|  |     if (typeof window === "undefined") return undefined; | ||||||
|  |  | ||||||
|  |     let theme: string; | ||||||
|  |     try { | ||||||
|  |       theme = localStorage.getItem(key) || undefined; | ||||||
|  |     } catch (e) {} // eslint-disable-line no-empty | ||||||
|  |  | ||||||
|  |     return theme || fallback; | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   // get the user's preferred theme via their OS/browser settings | ||||||
|  |   const getSystemTheme = (e?: MediaQueryList) => { | ||||||
|  |     if (!e) { | ||||||
|  |       e = window.matchMedia(darkModeQuery); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return e.matches ? "dark" : "light"; | ||||||
|  |   }; | ||||||
|  |  | ||||||
|   const [currentTheme, setCurrentTheme] = useState(() => getUserTheme(themeStorageKey, "system")); |   const [currentTheme, setCurrentTheme] = useState(() => getUserTheme(themeStorageKey, "system")); | ||||||
|   const [resolvedTheme, setResolvedTheme] = useState(() => getUserTheme(themeStorageKey)); |   const [resolvedTheme, setResolvedTheme] = useState(() => getUserTheme(themeStorageKey)); | ||||||
|  |  | ||||||
| @@ -131,7 +135,7 @@ export const ThemeProvider = ({ | |||||||
|     if (!enableColorScheme) return; |     if (!enableColorScheme) return; | ||||||
|  |  | ||||||
|     const colorScheme = |     const colorScheme = | ||||||
|       currentTheme && colorSchemes.includes(currentTheme) // light or dark |       currentTheme && themes.includes(currentTheme) // light or dark | ||||||
|         ? currentTheme |         ? currentTheme | ||||||
|         : // preference is unset, use the OS/browser setting |         : // preference is unset, use the OS/browser setting | ||||||
|         currentTheme === "system" |         currentTheme === "system" | ||||||
| @@ -146,13 +150,18 @@ export const ThemeProvider = ({ | |||||||
|   return ( |   return ( | ||||||
|     <ThemeContext.Provider |     <ThemeContext.Provider | ||||||
|       value={{ |       value={{ | ||||||
|  |         setTheme, | ||||||
|         themes: [...themes, "system"], |         themes: [...themes, "system"], | ||||||
|         theme: currentTheme, |         theme: currentTheme, | ||||||
|         resolvedTheme: currentTheme === "system" ? resolvedTheme : currentTheme, |         resolvedTheme: currentTheme === "system" ? resolvedTheme : currentTheme, | ||||||
|         setTheme, |  | ||||||
|       }} |       }} | ||||||
|     > |     > | ||||||
|       {children} |       {children} | ||||||
|     </ThemeContext.Provider> |     </ThemeContext.Provider> | ||||||
|   ); |   ); | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | // debugging help pls | ||||||
|  | if (process.env.NODE_ENV !== "production") { | ||||||
|  |   ThemeContext.displayName = "ThemeContext"; | ||||||
|  | } | ||||||
|   | |||||||
| @@ -1,16 +1,5 @@ | |||||||
| import { useContext } from "react"; | import { useContext } from "react"; | ||||||
| import { ThemeContext } from "../contexts/ThemeContext"; | import { ThemeContext } from "../contexts/ThemeContext"; | ||||||
|  |  | ||||||
| export interface UseThemeProps { | // convenience hook to get access to ThemeContext's state/functions from pages/components/etc. | ||||||
|   /** List of all available theme names */ | export const useTheme = () => useContext(ThemeContext); | ||||||
|   themes: string[]; |  | ||||||
|   /** Active theme name */ |  | ||||||
|   theme?: string; |  | ||||||
|   /** If the active theme is "system", this returns whether the system preference resolved to "dark" or "light". Otherwise, identical to `theme` */ |  | ||||||
|   resolvedTheme?: string; |  | ||||||
|   /** Update the theme */ |  | ||||||
|   setTheme: (theme: string) => void; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // useTheme() function to get current theme state from pages/components/etc. |  | ||||||
| export const useTheme = (): UseThemeProps => useContext(ThemeContext); |  | ||||||
|   | |||||||
| @@ -1,8 +1,8 @@ | |||||||
| import fs from "fs"; | import fs from "fs"; | ||||||
| import path from "path"; | import path from "path"; | ||||||
| import { renderToStaticMarkup } from "react-dom/server"; | import { renderToStaticMarkup } from "react-dom/server"; | ||||||
| import matter from "gray-matter"; |  | ||||||
| import { serialize } from "next-mdx-remote/serialize"; | import { serialize } from "next-mdx-remote/serialize"; | ||||||
|  | import matter from "gray-matter"; | ||||||
| import urlJoin from "url-join"; | import urlJoin from "url-join"; | ||||||
| import { minify } from "terser"; | import { minify } from "terser"; | ||||||
| import { compiler } from "markdown-to-jsx"; | import { compiler } from "markdown-to-jsx"; | ||||||
|   | |||||||
| @@ -12,9 +12,6 @@ export const themeColors = { | |||||||
|   dark: darkTheme.colors.backgroundOuter?.value, |   dark: darkTheme.colors.backgroundOuter?.value, | ||||||
| }; | }; | ||||||
|  |  | ||||||
| // default to a simple light or dark binary option |  | ||||||
| export const colorSchemes = ["light", "dark"]; |  | ||||||
|  |  | ||||||
| // https://web.dev/prefers-color-scheme/#the-prefers-color-scheme-media-query | // https://web.dev/prefers-color-scheme/#the-prefers-color-scheme-media-query | ||||||
| export const darkModeQuery = "(prefers-color-scheme: dark)"; | export const darkModeQuery = "(prefers-color-scheme: dark)"; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -57,7 +57,7 @@ const App = ({ Component, pageProps }: AppProps) => { | |||||||
|   const getLayout = Component.getLayout || ((page) => <Layout>{page}</Layout>); |   const getLayout = Component.getLayout || ((page) => <Layout>{page}</Layout>); | ||||||
|  |  | ||||||
|   return ( |   return ( | ||||||
|     <ThemeProvider classNames={themeClassNames}> |     <ThemeProvider classNames={themeClassNames} enableColorScheme={true}> | ||||||
|       {/* all SEO config is in ../lib/config/seo.ts except for canonical URLs, which require access to next router */} |       {/* all SEO config is in ../lib/config/seo.ts except for canonical URLs, which require access to next router */} | ||||||
|       <DefaultSeo |       <DefaultSeo | ||||||
|         {...defaultSeo} |         {...defaultSeo} | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user