diff --git a/src/layouts/bots/(partials)/rail-header.tsx b/src/layouts/bots/(partials)/rail-header.tsx index a958023..183284b 100644 --- a/src/layouts/bots/(partials)/rail-header.tsx +++ b/src/layouts/bots/(partials)/rail-header.tsx @@ -3,6 +3,7 @@ import Link from "@moduletrace/twui/layout/Link"; import Wordmark from "@/src/components/general/wordmark"; import LogoIcon from "@/src/components/general/logo-icon"; import RailAddButton from "./rail-add-button"; +import RailThemeToggle from "./rail-theme-toggle"; import { twMerge } from "tailwind-merge"; import { useContext } from "react"; import { AppContext } from "@/src/pages/__root"; @@ -47,7 +48,10 @@ export default function RailHeader({ show_add = true }: Props) { )} - {show_add ? : null} + + + {show_add ? : null} + ); } diff --git a/src/layouts/bots/(partials)/rail-theme-toggle.tsx b/src/layouts/bots/(partials)/rail-theme-toggle.tsx new file mode 100644 index 0000000..b785ea5 --- /dev/null +++ b/src/layouts/bots/(partials)/rail-theme-toggle.tsx @@ -0,0 +1,58 @@ +"use client"; + +import { useEffect, useState } from "react"; +import Button from "@moduletrace/twui/layout/Button"; +import { Moon, Sun } from "lucide-react"; +import useThemePreference from "@/src/hooks/use-theme-preference"; +import resolveTheme from "@/src/utils/resolve-theme"; + +const DarkQuery = "(prefers-color-scheme: dark)"; + +/** + * The rail header's scheme toggle: one click flips between light and dark + * for this device, stored where `ThemeInitScript` reads it. Following the + * device stays on Settings > General, since a toggle is always an explicit + * choice. It renders after mount only, because the painted scheme is + * decided on the client. + */ +export default function RailThemeToggle() { + const { preference, setPreference } = useThemePreference(); + const [mounted, setMounted] = useState(false); + const [system_is_dark, setSystemIsDark] = useState(false); + + useEffect(() => { + setMounted(true); + + const media = window.matchMedia(DarkQuery); + const onChange = () => setSystemIsDark(media.matches); + + onChange(); + media.addEventListener("change", onChange); + return () => media.removeEventListener("change", onChange); + }, []); + + if (!mounted) return null; + + const is_dark = + (preference + ? resolveTheme({ preference, system_is_dark }) + : system_is_dark + ? "dark" + : "light") == "dark"; + const title = is_dark ? "Switch to the light theme" : "Switch to the dark theme"; + const Icon = is_dark ? Moon : Sun; + + return ( + + ); +}