Skip to content

Commit

Permalink
refactor theme
Browse files Browse the repository at this point in the history
  • Loading branch information
nichtsam committed Apr 1, 2024
1 parent 7e5d1ea commit e675ffc
Show file tree
Hide file tree
Showing 6 changed files with 90 additions and 100 deletions.
2 changes: 1 addition & 1 deletion app/components/navbar/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { ThemeSwitcher } from "../theme-switcher.tsx";
import { MobileNavigation } from "./mobile.tsx";
import { CORE_CONTENT_LINKS } from "./constant.ts";
import {
Expand All @@ -10,6 +9,7 @@ import {
} from "../ui/navigation-menu.tsx";
import { NavLink } from "../link.tsx";
import { UserButton } from "../user.tsx";
import { ThemeSwitcher } from "#app/utils/theme.tsx";

export const NavBar = () => {
return (
Expand Down
19 changes: 17 additions & 2 deletions app/root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type {
MetaFunction,
LinksFunction,
LoaderFunctionArgs,
ActionFunctionArgs,
} from "@remix-run/node";
import {
Links,
Expand All @@ -24,8 +25,8 @@ import {
import { FaviconMeta, faviconLinks } from "#app/utils/favicon.tsx";
import { useNonce } from "./utils/nonce-provider.tsx";
import { ClientHintsCheck, getHints } from "./utils/client-hints.tsx";
import { getTheme, type Theme } from "./utils/theme.server.ts";
import { useTheme } from "./utils/theme.ts";
import { setTheme, getTheme, type Theme } from "./utils/theme.server.ts";
import { SET_THEME_INTENT, useTheme } from "./utils/theme.tsx";
import { TooltipProvider } from "./components/ui/tooltip.tsx";
import { GeneralErrorBoundary } from "./components/error-boundary.tsx";
import { NavProgress } from "./components/nav-progress.tsx";
Expand Down Expand Up @@ -92,6 +93,20 @@ export const loader = async ({ request }: LoaderFunctionArgs) => {
);
};

export const action = async (args: ActionFunctionArgs) => {
const formData = await args.request.formData();
const intent = formData.get("intent");

switch (intent) {
case SET_THEME_INTENT: {
return setTheme(formData);
}
default: {
throw new Response(`Invalid intent "${intent}"`, { status: 400 });
}
}
};

function Document({
children,
nonce,
Expand Down
49 changes: 0 additions & 49 deletions app/routes/action.set-theme.tsx

This file was deleted.

25 changes: 24 additions & 1 deletion app/utils/theme.server.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { parseWithZod } from "@conform-to/zod";
import { json } from "@remix-run/node";
import { parse as parseCookie, serialize as serializeCookie } from "cookie";
import { ThemeFormSchema } from "./theme";

const cookieName = "theme";
export type Theme = "light" | "dark";
Expand All @@ -12,10 +15,30 @@ export const getTheme = (request: Request): Theme | null => {
return null;
};

export const setTheme = (theme: Theme | "system"): string => {
const setThemeCookie = (theme: Theme | "system"): string => {
if (theme === "system") {
return serializeCookie(cookieName, "", { path: "/", maxAge: -1 });
} else {
return serializeCookie(cookieName, theme, { path: "/" });
}
};

export const setTheme = async (formData: FormData) => {
const submission = parseWithZod(formData, {
schema: ThemeFormSchema,
});

if (submission.status !== "success") {
return json(
{ result: submission.reply() },
{ status: submission.status === "error" ? 400 : 200 },
);
}

const { theme } = submission.value;

const responseInit = {
headers: { "set-cookie": setThemeCookie(theme) },
};
return json({ result: submission.reply() }, responseInit);
};
34 changes: 0 additions & 34 deletions app/utils/theme.ts

This file was deleted.

61 changes: 48 additions & 13 deletions app/components/theme-switcher.tsx → app/utils/theme.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,49 @@
import { LaptopIcon, MoonIcon, SunIcon } from "@radix-ui/react-icons";
import { useFetcher } from "@remix-run/react";
import { useForm, getFormProps } from "@conform-to/react";
import type { action as setThemeAction } from "#app/routes/action.set-theme.tsx";
import { useOptimisticThemeMode } from "#app/utils/theme.ts";
import { useRequestInfo } from "#app/utils/request-info.ts";
import { Button } from "./ui/button.tsx";
import { useFetcher, useFetchers } from "@remix-run/react";
import { useRequestInfo } from "./request-info.ts";
import { parseWithZod } from "@conform-to/zod";
import { useHints } from "./client-hints.tsx";
import { z } from "zod";
import { useEffect, useState } from "react";
import { unvariant } from "#app/utils/misc.ts";
import { type setTheme as setThemeAction } from "./theme.server.ts";
import { getFormProps, useForm } from "@conform-to/react";
import { LaptopIcon, MoonIcon, SunIcon } from "@radix-ui/react-icons";
import { Button } from "#app/components/ui/button.tsx";
import { unvariant } from "./misc.ts";

export const SET_THEME_INTENT = "set-theme";

export const ThemeFormSchema = z.object({
theme: z.enum(["system", "light", "dark"]),
});

export const useTheme = () => {
const requestInfo = useRequestInfo();
const hints = useHints();

const optimisticMode = useOptimisticThemeMode();
if (optimisticMode) {
return optimisticMode === "system" ? hints.theme : optimisticMode;
}

return requestInfo.userPreferences.theme ?? hints.theme;
};

export function useOptimisticThemeMode() {
const fetchers = useFetchers();
const themeFetcher = fetchers.find(
(f) => f.formAction === "/action/set-theme",
);

if (themeFetcher && themeFetcher.formData) {
const submission = parseWithZod(themeFetcher.formData, {
schema: ThemeFormSchema,
});

if (submission.status === "success") {
return submission.value.theme;
}
}
}

export const ThemeSwitcher = () => {
const [clientJavascriptEnable, setClientJavascriptEnable] = useState(false);
Expand Down Expand Up @@ -36,14 +73,12 @@ export const ThemeSwitcher = () => {
}, []);

return (
<fetcher.Form
method="POST"
action="/action/set-theme"
{...getFormProps(form)}
>
<fetcher.Form method="POST" action="/" {...getFormProps(form)}>
<input type="hidden" name="theme" value={nextMode} />

<Button
name="intent"
value={SET_THEME_INTENT}
type="submit"
size="icon"
variant="ghost"
Expand Down

0 comments on commit e675ffc

Please sign in to comment.