From 379ef35e08a5002b32738ac1808c5c4eec424fba Mon Sep 17 00:00:00 2001 From: Benjamin Toby Date: Thu, 22 Oct 2026 12:02:04 +0100 Subject: [PATCH] Toggle the color scheme from the rail header Add a moon/sun button beside the rail's Add icon that flips this device between light and dark, stored under the same theme key the theme picker and ThemeInitScript read. Following the device stays on Settings > General, since the toggle is always an explicit choice. --- src/layouts/bots/(partials)/rail-header.tsx | 6 +- .../bots/(partials)/rail-theme-toggle.tsx | 58 +++++++++++++++++++ 2 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 src/layouts/bots/(partials)/rail-theme-toggle.tsx 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 ( + + ); +}