From 37024d956587981454ee9c6710879a560b4dcd16 Mon Sep 17 00:00:00 2001 From: Matt Kane Date: Fri, 11 Sep 2026 17:47:02 +0100 Subject: [PATCH 1/2] fix(admin): simplify theme toggle --- .changeset/calm-themes-follow.md | 5 ++ .../admin/src/components/ThemeProvider.tsx | 6 +- packages/admin/src/components/ThemeToggle.tsx | 39 ++++------- .../admin/tests/components/Header.test.tsx | 4 +- .../tests/components/ThemeToggle.test.tsx | 65 +++++++++++-------- 5 files changed, 65 insertions(+), 54 deletions(-) create mode 100644 .changeset/calm-themes-follow.md diff --git a/.changeset/calm-themes-follow.md b/.changeset/calm-themes-follow.md new file mode 100644 index 0000000000..0293c32e81 --- /dev/null +++ b/.changeset/calm-themes-follow.md @@ -0,0 +1,5 @@ +--- +"@emdash-cms/admin": patch +--- + +Fixes the admin appearance toggle so every click changes the visible color scheme. The admin follows the system preference whenever the selected appearance matches it. diff --git a/packages/admin/src/components/ThemeProvider.tsx b/packages/admin/src/components/ThemeProvider.tsx index 3e6a1a3cfb..277bfe9c4a 100644 --- a/packages/admin/src/components/ThemeProvider.tsx +++ b/packages/admin/src/components/ThemeProvider.tsx @@ -79,7 +79,11 @@ export function ThemeProvider({ children, defaultTheme = "system" }: ThemeProvid const setTheme = React.useCallback((newTheme: Theme) => { setThemeState(newTheme); - localStorage.setItem(STORAGE_KEY, newTheme); + if (newTheme === "system") { + localStorage.removeItem(STORAGE_KEY); + } else { + localStorage.setItem(STORAGE_KEY, newTheme); + } }, []); const value = React.useMemo( diff --git a/packages/admin/src/components/ThemeToggle.tsx b/packages/admin/src/components/ThemeToggle.tsx index fc084718ad..fcb0dc86c6 100644 --- a/packages/admin/src/components/ThemeToggle.tsx +++ b/packages/admin/src/components/ThemeToggle.tsx @@ -1,45 +1,34 @@ import { Button } from "@cloudflare/kumo"; import { useLingui } from "@lingui/react/macro"; -import { Sun, Moon, Monitor } from "@phosphor-icons/react"; -import * as React from "react"; +import { Sun, Moon } from "@phosphor-icons/react"; import { useTheme } from "./ThemeProvider"; -/** - * Theme toggle button that cycles through: system -> light -> dark - */ export function ThemeToggle() { const { t } = useLingui(); - const { theme, setTheme, resolvedTheme } = useTheme(); + const { setTheme, resolvedTheme } = useTheme(); - const cycleTheme = () => { - const order: ["system", "light", "dark"] = ["system", "light", "dark"]; - const currentIndex = order.indexOf(theme); - const nextIndex = (currentIndex + 1) % order.length; - setTheme(order[nextIndex]!); + const toggleTheme = () => { + const nextTheme = resolvedTheme === "light" ? "dark" : "light"; + const systemTheme = window.matchMedia("(prefers-color-scheme: dark)").matches + ? "dark" + : "light"; + setTheme(nextTheme === systemTheme ? "system" : nextTheme); }; - const resolvedLabel = resolvedTheme === "light" ? t`light` : t`dark`; - const label = - theme === "system" ? t`System (${resolvedLabel})` : theme === "light" ? t`Light` : t`Dark`; + const label = resolvedTheme === "light" ? t`Switch to dark` : t`Switch to light`; return ( ); } diff --git a/packages/admin/tests/components/Header.test.tsx b/packages/admin/tests/components/Header.test.tsx index 7ecdb9e637..36f18fd80f 100644 --- a/packages/admin/tests/components/Header.test.tsx +++ b/packages/admin/tests/components/Header.test.tsx @@ -53,7 +53,7 @@ const { Header } = await import("../../src/components/Header"); // Constants // --------------------------------------------------------------------------- -const THEME_BUTTON_REGEX = /Toggle theme/; +const THEME_BUTTON_REGEX = /Switch to (light|dark)/; function TestWrapper({ children }: { children: React.ReactNode }) { const qc = new QueryClient({ @@ -80,7 +80,7 @@ describe("Header", () => {
, ); - // ThemeToggle renders a button with aria-label "Toggle theme (current: …)" + // ThemeToggle exposes its next action in the aria-label. // (Kumo 2.x wraps `