import { useMemo } from "react";
import { useFetcher, useRouteLoaderData } from "react-router";
import { createTranslator } from "~/i18n/t";
import type { AppTheme } from "~/lib/theme";
import type { Locale } from "../LanguageSwitcher";
import { cx, FOCUS_RING, HOVER_TRANSITION } from "./styles";
/** The document surface the root loader currently paints on . */
interface RootLoaderData {
theme?: AppTheme;
}
/** A minimal sun, for "switch to light". Decorative - the label calls it. */
function SunIcon() {
return (
);
}
/** A minimal crescent moon, for "switch to dark". Decorative. */
function MoonIcon() {
return (
);
}
export interface ThemeToggleProps {
/** Reading locale, for the accessible label. */
locale: Locale;
className?: string;
}
/**
* The site's one global theme control: sun in dark mode, moon in light.
*
* It renders the target of the next click - the label and the icon describe
* what a press DOES ("Switch to light theme"), never the current state - and
* posts that target to the root action at `/`. The action persists the
* `sharper-theme` cookie, the loader re-runs and the shell repaints
* `data-surface` on , so the whole tier-2 palette re-resolves. Because
* the current theme is read off the root loader (which the server fed from
* the cookie), the SSR markup and the first client render always agree.
*/
export function ThemeToggle({ locale, className }: ThemeToggleProps) {
const fetcher = useFetcher();
const rootData = useRouteLoaderData("root");
const t = useMemo(() => createTranslator(locale), [locale]);
const theme: AppTheme = rootData?.theme ?? "dark";
const targetTheme: AppTheme = theme === "light" ? "dark" : "light";
const label =
targetTheme === "light" ? t("nav.themeToLight") : t("nav.themeToDark");
return (
);
}
export default ThemeToggle;
/* ---------------------------------------------------------------------------
USAGE
---------------------------------------------------------------------------
// In the nav actions cluster, between the language control and the
// hamburger. Reads the theme from the root loader on its own.
------------------------------------------------------------------------ */