feat: custom window titlebar — remove native decorations, add WindowControls to TopBar

This commit is contained in:
Your Name
2026-02-15 20:56:55 +02:00
parent 4b70afae5f
commit af529a2d99
3 changed files with 77 additions and 1 deletions

View File

@@ -16,7 +16,8 @@
"width": 1200,
"height": 800,
"minWidth": 800,
"minHeight": 600
"minHeight": 600,
"decorations": false
}
],
"security": {

View File

@@ -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() {
</TooltipTrigger>
<TooltipContent>Settings</TooltipContent>
</Tooltip>
<WindowControls />
</div>
</header>
);

View File

@@ -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 (
<div className="flex items-center">
{/* Separator */}
<div className="mx-2 h-4 w-px bg-pylon-text-secondary/20" />
{/* Minimize */}
<motion.button
onClick={() => 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"
>
<Minus className="size-4" />
</motion.button>
{/* Maximize / Restore */}
<motion.button
onClick={() => 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 ? (
<Copy className="size-3.5" />
) : (
<Square className="size-3.5" />
)}
</motion.button>
{/* Close */}
<motion.button
onClick={() => 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"
>
<X className="size-4" />
</motion.button>
</div>
);
}