feat: custom window titlebar - remove native decorations, add WindowControls to TopBar
This commit is contained in:
@@ -16,7 +16,8 @@
|
|||||||
"width": 1200,
|
"width": 1200,
|
||||||
"height": 800,
|
"height": 800,
|
||||||
"minWidth": 800,
|
"minWidth": 800,
|
||||||
"minHeight": 600
|
"minHeight": 600,
|
||||||
|
"decorations": false
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"security": {
|
"security": {
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import {
|
|||||||
} from "@/components/ui/dropdown-menu";
|
} from "@/components/ui/dropdown-menu";
|
||||||
import { useAppStore } from "@/stores/app-store";
|
import { useAppStore } from "@/stores/app-store";
|
||||||
import { useBoardStore } from "@/stores/board-store";
|
import { useBoardStore } from "@/stores/board-store";
|
||||||
|
import { WindowControls } from "@/components/layout/WindowControls";
|
||||||
|
|
||||||
export function TopBar() {
|
export function TopBar() {
|
||||||
const view = useAppStore((s) => s.view);
|
const view = useAppStore((s) => s.view);
|
||||||
@@ -238,6 +239,7 @@ export function TopBar() {
|
|||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>Settings</TooltipContent>
|
<TooltipContent>Settings</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
<WindowControls />
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
);
|
);
|
||||||
|
|||||||
73
src/components/layout/WindowControls.tsx
Normal file
73
src/components/layout/WindowControls.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user