feat(desktop): hide native titlebar on Windows/Linux with overlay window controls

Use titleBarStyle: 'hidden' on all platforms. On Windows/Linux, enable
titleBarOverlay with dark theme colors for native min/max/close buttons.
Extend useTrafficLightPadding to return side ('left'|'right'|null) so
consumers apply padding on the correct side per platform.
This commit is contained in:
Mohamed Boudra
2026-03-27 19:59:17 +07:00
parent 95ad879f82
commit ab3443fd52
7 changed files with 53 additions and 18 deletions

View File

@@ -6,7 +6,6 @@ import {
HEADER_INNER_HEIGHT,
HEADER_INNER_HEIGHT_MOBILE,
HEADER_TOP_PADDING_MOBILE,
getIsDesktopMac,
} from "@/constants/layout";
import { useDesktopDragHandlers, useTrafficLightPadding } from "@/utils/desktop-window";
import { usePanelStore } from "@/stores/panel-store";
@@ -31,8 +30,10 @@ export function ScreenHeader({ left, right, leftStyle, rightStyle }: ScreenHeade
// Only add extra padding on mobile for better touch targets; on desktop, only use safe area insets
const topPadding = isMobile ? HEADER_TOP_PADDING_MOBILE : 0;
const baseHorizontalPadding = theme.spacing[2];
const collapsedSidebarTrafficLightInset =
!isMobile && !desktopAgentListOpen && getIsDesktopMac() ? trafficLightPadding.left : 0;
const collapsedSidebarInset =
!isMobile && !desktopAgentListOpen && trafficLightPadding.side
? trafficLightPadding
: { left: 0, right: 0 };
const dragHandlers = useDesktopDragHandlers();
@@ -42,7 +43,10 @@ export function ScreenHeader({ left, right, leftStyle, rightStyle }: ScreenHeade
<View
style={[
styles.row,
{ paddingLeft: baseHorizontalPadding + collapsedSidebarTrafficLightInset },
{
paddingLeft: baseHorizontalPadding + collapsedSidebarInset.left,
paddingRight: baseHorizontalPadding + collapsedSidebarInset.right,
},
]}
{...dragHandlers}
>

View File

@@ -681,7 +681,7 @@ function DesktopSidebar({
return (
<Animated.View style={[styles.desktopSidebar, resizeAnimatedStyle]}>
{trafficLightPadding.top > 0 ? (
{trafficLightPadding.side === 'left' ? (
<View style={{ height: trafficLightPadding.top }} {...dragHandlers} />
) : null}
<View style={styles.sidebarHeader} {...dragHandlers}>

View File

@@ -886,7 +886,10 @@ function SplitPaneView({
style={[
styles.paneTabs,
isFocusModeEnabled &&
trafficLightPadding.left > 0 && { paddingLeft: trafficLightPadding.left },
trafficLightPadding.side && {
paddingLeft: trafficLightPadding.left,
paddingRight: trafficLightPadding.right,
},
]}
>
<WorkspaceDesktopTabsRow

View File

@@ -19,6 +19,10 @@ export const MAX_CONTENT_WIDTH = 820;
export const DESKTOP_TRAFFIC_LIGHT_WIDTH = 78;
export const DESKTOP_TRAFFIC_LIGHT_HEIGHT = 45;
// Windows/Linux window controls (minimize/maximize/close) — top-right
export const DESKTOP_WINDOW_CONTROLS_WIDTH = 140;
export const DESKTOP_WINDOW_CONTROLS_HEIGHT = 48;
// Check if running in desktop app (any OS)
function isDesktopEnvironment(): boolean {
if (Platform.OS !== "web") return false;

View File

@@ -7,7 +7,6 @@ import { PaseoLogo } from "@/components/icons/paseo-logo";
import { SidebarMenuToggle } from "@/components/headers/menu-header";
import { useOpenProjectPicker } from "@/hooks/use-open-project-picker";
import { usePanelStore } from "@/stores/panel-store";
import { getIsDesktopMac } from "@/constants/layout";
import { useDesktopDragHandlers, useTrafficLightPadding } from "@/utils/desktop-window";
export function OpenProjectScreen({ serverId }: { serverId: string }) {
@@ -19,8 +18,10 @@ export function OpenProjectScreen({ serverId }: { serverId: string }) {
const openProjectPicker = useOpenProjectPicker(serverId);
const isMobile = UnistylesRuntime.breakpoint === "xs" || UnistylesRuntime.breakpoint === "sm";
const needsTrafficLightInset = !isMobile && !desktopAgentListOpen && getIsDesktopMac();
const trafficLightInset = needsTrafficLightInset ? trafficLightPadding.left : 0;
const collapsedSidebarInset =
!isMobile && !desktopAgentListOpen && trafficLightPadding.side
? trafficLightPadding
: { left: 0, right: 0 };
const dragHandlers = useDesktopDragHandlers();
useEffect(() => {
@@ -31,7 +32,7 @@ export function OpenProjectScreen({ serverId }: { serverId: string }) {
return (
<View style={styles.container} {...dragHandlers}>
<View style={[styles.menuToggle, { paddingTop: insets.top, paddingLeft: trafficLightInset }]}>
<View style={[styles.menuToggle, { paddingTop: insets.top, paddingLeft: collapsedSidebarInset.left, paddingRight: collapsedSidebarInset.right }]}>
<SidebarMenuToggle />
</View>
<View style={styles.content}>

View File

@@ -2,8 +2,11 @@ import { useEffect, useMemo, useRef, useState } from "react";
import { Platform, type PointerEvent as RNPointerEvent, type ViewProps } from "react-native";
import {
getIsDesktopMac,
getIsDesktop,
DESKTOP_TRAFFIC_LIGHT_WIDTH,
DESKTOP_TRAFFIC_LIGHT_HEIGHT,
DESKTOP_WINDOW_CONTROLS_WIDTH,
DESKTOP_WINDOW_CONTROLS_HEIGHT,
} from "@/constants/layout";
import { getDesktopWindow } from "@/desktop/electron/window";
import { isDesktop } from "@/desktop/host";
@@ -120,11 +123,11 @@ export function useDesktopDragHandlers(): DesktopDragViewProps {
}, [isActive]);
}
export function useTrafficLightPadding(): { left: number; top: number } {
export function useTrafficLightPadding(): { left: number; right: number; top: number; side: 'left' | 'right' | null } {
const [isFullscreen, setIsFullscreen] = useState(false);
useEffect(() => {
if (Platform.OS !== "web" || !getIsDesktopMac()) return;
if (Platform.OS !== "web" || !getIsDesktop()) return;
let disposed = false;
let cleanup: (() => void) | undefined;
@@ -175,12 +178,23 @@ export function useTrafficLightPadding(): { left: number; top: number } {
};
}, []);
if (!getIsDesktopMac() || isFullscreen) {
return { left: 0, top: 0 };
if (!getIsDesktop() || isFullscreen) {
return { left: 0, right: 0, top: 0, side: null };
}
if (getIsDesktopMac()) {
return {
left: DESKTOP_TRAFFIC_LIGHT_WIDTH,
right: 0,
top: DESKTOP_TRAFFIC_LIGHT_HEIGHT,
side: 'left',
};
}
return {
left: DESKTOP_TRAFFIC_LIGHT_WIDTH,
top: DESKTOP_TRAFFIC_LIGHT_HEIGHT,
left: 0,
right: DESKTOP_WINDOW_CONTROLS_WIDTH,
top: DESKTOP_WINDOW_CONTROLS_HEIGHT,
side: 'right',
};
}

View File

@@ -92,8 +92,17 @@ async function createMainWindow(): Promise<void> {
height: 800,
show: false,
...(iconPath ? { icon: iconPath } : {}),
titleBarStyle: isMac ? "hidden" : "default",
trafficLightPosition: isMac ? { x: 16, y: 14 } : undefined,
titleBarStyle: "hidden",
...(isMac
? { trafficLightPosition: { x: 16, y: 14 } }
: {
titleBarOverlay: {
color: "#18181c",
symbolColor: "#e4e4e7",
height: 48,
},
autoHideMenuBar: true,
}),
webPreferences: {
preload: getPreloadPath(),
contextIsolation: true,