From af529a2d99cbbced327ef4ff5f51103812f04f76 Mon Sep 17 00:00:00 2001 From: Your Name Date: Sun, 15 Feb 2026 20:56:55 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20custom=20window=20titlebar=20=E2=80=94?= =?UTF-8?q?=20remove=20native=20decorations,=20add=20WindowControls=20to?= =?UTF-8?q?=20TopBar?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src-tauri/tauri.conf.json | 3 +- src/components/layout/TopBar.tsx | 2 + src/components/layout/WindowControls.tsx | 73 ++++++++++++++++++++++++ 3 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 src/components/layout/WindowControls.tsx diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 9fdef43..8f22442 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -16,7 +16,8 @@ "width": 1200, "height": 800, "minWidth": 800, - "minHeight": 600 + "minHeight": 600, + "decorations": false } ], "security": { diff --git a/src/components/layout/TopBar.tsx b/src/components/layout/TopBar.tsx index 083d855..3e452f7 100644 --- a/src/components/layout/TopBar.tsx +++ b/src/components/layout/TopBar.tsx @@ -17,6 +17,7 @@ import { } from "@/components/ui/dropdown-menu"; import { useAppStore } from "@/stores/app-store"; import { useBoardStore } from "@/stores/board-store"; +import { WindowControls } from "@/components/layout/WindowControls"; export function TopBar() { const view = useAppStore((s) => s.view); @@ -238,6 +239,7 @@ export function TopBar() { Settings + ); diff --git a/src/components/layout/WindowControls.tsx b/src/components/layout/WindowControls.tsx new file mode 100644 index 0000000..f1c46c3 --- /dev/null +++ b/src/components/layout/WindowControls.tsx @@ -0,0 +1,73 @@ +import { useState, useEffect } from "react"; +import { getCurrentWindow } from "@tauri-apps/api/window"; +import { motion } from "framer-motion"; +import { Minus, Square, Copy, X } from "lucide-react"; +import { springs } from "@/lib/motion"; + +export function WindowControls() { + const [isMaximized, setIsMaximized] = useState(false); + + useEffect(() => { + const appWindow = getCurrentWindow(); + + appWindow.isMaximized().then(setIsMaximized); + + const unlisten = appWindow.onResized(async () => { + const maximized = await appWindow.isMaximized(); + setIsMaximized(maximized); + }); + + return () => { + unlisten.then((fn) => fn()); + }; + }, []); + + const appWindow = getCurrentWindow(); + + return ( +
+ {/* Separator */} +
+ + {/* Minimize */} + appWindow.minimize()} + className="flex size-8 items-center justify-center rounded-md text-pylon-text-secondary transition-colors hover:bg-pylon-accent/10 hover:text-pylon-text" + whileHover={{ scale: 1.1 }} + whileTap={{ scale: 0.9 }} + transition={springs.snappy} + aria-label="Minimize" + > + + + + {/* Maximize / Restore */} + appWindow.toggleMaximize()} + className="flex size-8 items-center justify-center rounded-md text-pylon-text-secondary transition-colors hover:bg-pylon-accent/10 hover:text-pylon-text" + whileHover={{ scale: 1.1 }} + whileTap={{ scale: 0.9 }} + transition={springs.snappy} + aria-label={isMaximized ? "Restore" : "Maximize"} + > + {isMaximized ? ( + + ) : ( + + )} + + + {/* Close */} + appWindow.close()} + className="flex size-8 items-center justify-center rounded-md text-pylon-text-secondary transition-colors hover:bg-pylon-danger/15 hover:text-pylon-danger" + whileHover={{ scale: 1.1 }} + whileTap={{ scale: 0.9 }} + transition={springs.snappy} + aria-label="Close" + > + + +
+ ); +}