File size: 3,368 Bytes
1f21206 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 | import { useState, useEffect } from 'react'
const isTauri = typeof window !== 'undefined' && ('__TAURI_INTERNALS__' in window || '__TAURI__' in window)
const isWindows = typeof navigator !== 'undefined' && /Win/.test(navigator.platform)
/** Whether to render custom window controls (Windows + Tauri only) */
export const showWindowControls = isTauri && isWindows
export function WindowControls() {
const [maximized, setMaximized] = useState(false)
const [win, setWin] = useState<{
minimize: () => Promise<void>
toggleMaximize: () => Promise<void>
close: () => Promise<void>
isMaximized: () => Promise<boolean>
onResized: (handler: () => void) => Promise<() => void>
} | null>(null)
useEffect(() => {
if (!showWindowControls) return
let unlisten: (() => void) | undefined
import('@tauri-apps/api/window')
.then(async ({ getCurrentWindow }) => {
const w = getCurrentWindow()
setWin(w as any)
setMaximized(await w.isMaximized())
unlisten = await w.onResized(async () => {
setMaximized(await w.isMaximized())
})
})
.catch(() => {})
return () => { unlisten?.() }
}, [])
const runWindowAction = (action: () => Promise<void>) => {
void action().catch((error) => {
console.error('Window control action failed', error)
})
}
if (!showWindowControls || !win) return null
return (
<div data-testid="window-controls" className="flex items-stretch flex-shrink-0 -my-px">
{/* Minimize */}
<button
onClick={() => runWindowAction(() => win.minimize())}
aria-label="Minimize window"
className="w-[46px] h-full flex items-center justify-center text-[var(--color-text-secondary)] hover:bg-[var(--color-surface-hover)] transition-colors"
>
<svg width="10" height="1" viewBox="0 0 10 1">
<rect width="10" height="1" fill="currentColor" />
</svg>
</button>
{/* Maximize / Restore */}
<button
onClick={() => runWindowAction(() => win.toggleMaximize())}
aria-label={maximized ? 'Restore window' : 'Maximize window'}
className="w-[46px] h-full flex items-center justify-center text-[var(--color-text-secondary)] hover:bg-[var(--color-surface-hover)] transition-colors"
>
{maximized ? (
<svg width="10" height="10" viewBox="0 0 10 10" fill="none" stroke="currentColor" strokeWidth="1">
<rect x="0" y="3" width="7" height="7" />
<polyline points="3,3 3,0 10,0 10,7 7,7" />
</svg>
) : (
<svg width="10" height="10" viewBox="0 0 10 10" fill="none" stroke="currentColor" strokeWidth="1">
<rect x="0.5" y="0.5" width="9" height="9" />
</svg>
)}
</button>
{/* Close */}
<button
onClick={() => runWindowAction(() => win.close())}
aria-label="Close window"
className="w-[46px] h-full flex items-center justify-center text-[var(--color-text-secondary)] hover:bg-[var(--color-window-close-hover)] hover:text-white transition-colors"
>
<svg width="10" height="10" viewBox="0 0 10 10" fill="none" stroke="currentColor" strokeWidth="1.2">
<line x1="0" y1="0" x2="10" y2="10" />
<line x1="10" y1="0" x2="0" y2="10" />
</svg>
</button>
</div>
)
}
|