From a9a8f8422eabfd0aae39a7396b9e1723d831666f Mon Sep 17 00:00:00 2001 From: LyAhn Date: Mon, 29 Jun 2026 20:26:08 +0100 Subject: [PATCH] feat(ui): quick theme switch from the settings cog Right-clicking the settings cog in the title bar opens a small theme menu (Phokus / Subtle Light / Conventional Dark) with the active theme checked, anchored under the cog and dismissed on outside-click or Escape. Left-click still opens Settings. Keeps theme switching one gesture away without cluttering the title bar with another icon. --- CHANGELOG.md | 3 ++ src/components/TitleBar.tsx | 75 +++++++++++++++++++++++++++++++++++-- 2 files changed, 74 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 91ec441..8efd20c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,9 @@ aims to follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html) ### Added +- **Quick theme switch** — right-click the settings cog in the title bar to + switch theme (Phokus / Subtle Light / Conventional Dark) on the spot, without + opening Settings. - **Albums** — curate your own collections. A new Albums section in the sidebar (with cover thumbnails, kept visually distinct from Libraries) lets you create, rename, reorder (drag the row), and open albums; albums can span multiple diff --git a/src/components/TitleBar.tsx b/src/components/TitleBar.tsx index 1ebc005..1ab5728 100644 --- a/src/components/TitleBar.tsx +++ b/src/components/TitleBar.tsx @@ -1,9 +1,15 @@ -import { useState, useEffect } from "react"; +import { useState, useEffect, useRef } from "react"; import { getCurrentWindow } from "@tauri-apps/api/window"; -import { useGalleryStore } from "../store"; +import { useGalleryStore, AppTheme } from "../store"; import { PhokusMark } from "./PhokusMark"; import { Tooltip } from "./Tooltip"; +const THEME_OPTIONS: { value: AppTheme; label: string }[] = [ + { value: "phokus", label: "Phokus" }, + { value: "subtle-light", label: "Subtle Light" }, + { value: "conventional-dark", label: "Conventional Dark" }, +]; + // SVG icons for window controls function MinimizeIcon() { return ( @@ -41,11 +47,40 @@ function CloseIcon() { export function TitleBar() { const [isMaximized, setIsMaximized] = useState(false); const setSettingsOpen = useGalleryStore((state) => state.setSettingsOpen); + const theme = useGalleryStore((state) => state.theme); + const setTheme = useGalleryStore((state) => state.setTheme); const updateStatus = useGalleryStore((state) => state.updateStatus); const updateVersion = useGalleryStore((state) => state.updateVersion); const installUpdate = useGalleryStore((state) => state.installUpdate); const appWindow = getCurrentWindow(); + // Right-clicking the settings cog opens a quick theme switcher, anchored under + // the cog so it never overflows the right edge of the window. + const settingsBtnRef = useRef(null); + const themeMenuRef = useRef(null); + const [themeMenu, setThemeMenu] = useState<{ top: number; right: number } | null>(null); + + useEffect(() => { + if (!themeMenu) return; + const handleDown = (e: MouseEvent) => { + if (themeMenuRef.current && !themeMenuRef.current.contains(e.target as Node)) setThemeMenu(null); + }; + const handleKey = (e: KeyboardEvent) => { if (e.key === "Escape") setThemeMenu(null); }; + document.addEventListener("mousedown", handleDown); + document.addEventListener("keydown", handleKey); + return () => { + document.removeEventListener("mousedown", handleDown); + document.removeEventListener("keydown", handleKey); + }; + }, [themeMenu]); + + const handleSettingsContextMenu = (e: React.MouseEvent) => { + e.preventDefault(); + const rect = settingsBtnRef.current?.getBoundingClientRect(); + if (!rect) return; + setThemeMenu({ top: rect.bottom + 4, right: window.innerWidth - rect.right }); + }; + useEffect(() => { // Get initial maximized state appWindow.isMaximized().then(setIsMaximized); @@ -109,9 +144,11 @@ export function TitleBar() { className="flex items-stretch h-full" style={{ WebkitAppRegion: "no-drag" } as React.CSSProperties} > + - + {/* Minimize */} + + {/* Quick theme switcher — opened by right-clicking the settings cog. */} + {themeMenu && ( +
+
Theme
+ {THEME_OPTIONS.map((opt) => { + const active = theme === opt.value; + return ( + + ); + })} +
+ )} ); }