1
mirror of https://github.com/jakejarvis/jarv.is.git synced 2025-04-26 18:48:28 -04:00

consolidate theme context/provider types

This commit is contained in:
Jake Jarvis 2022-04-08 12:36:19 -04:00
parent a8c1a3ba3c
commit d09cf7ab26
Signed by: jake
GPG Key ID: 2B0C9CF251E69A39
6 changed files with 56 additions and 64 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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