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:
parent
a8c1a3ba3c
commit
d09cf7ab26
@ -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}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user