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
@@ -6,7 +6,6 @@ import {
HEADER_INNER_HEIGHT, HEADER_INNER_HEIGHT,
HEADER_INNER_HEIGHT_MOBILE, HEADER_INNER_HEIGHT_MOBILE,
HEADER_TOP_PADDING_MOBILE, HEADER_TOP_PADDING_MOBILE,
getIsDesktopMac,
} from "@/constants/layout"; } from "@/constants/layout";
import { useDesktopDragHandlers, useTrafficLightPadding } from "@/utils/desktop-window"; import { useDesktopDragHandlers, useTrafficLightPadding } from "@/utils/desktop-window";
import { usePanelStore } from "@/stores/panel-store"; 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 // 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 topPadding = isMobile ? HEADER_TOP_PADDING_MOBILE : 0;
const baseHorizontalPadding = theme.spacing[2]; const baseHorizontalPadding = theme.spacing[2];
const collapsedSidebarTrafficLightInset = const collapsedSidebarInset =
!isMobile && !desktopAgentListOpen && getIsDesktopMac() ? trafficLightPadding.left : 0; !isMobile && !desktopAgentListOpen && trafficLightPadding.side
? trafficLightPadding
: { left: 0, right: 0 };
const dragHandlers = useDesktopDragHandlers(); const dragHandlers = useDesktopDragHandlers();
@@ -42,7 +43,10 @@ export function ScreenHeader({ left, right, leftStyle, rightStyle }: ScreenHeade
<View <View
style={[ style={[
styles.row, styles.row,
{ paddingLeft: baseHorizontalPadding + collapsedSidebarTrafficLightInset }, {
paddingLeft: baseHorizontalPadding + collapsedSidebarInset.left,
paddingRight: baseHorizontalPadding + collapsedSidebarInset.right,
},
]} ]}
{...dragHandlers} {...dragHandlers}
> >
+1 -1
View File
@@ -681,7 +681,7 @@ function DesktopSidebar({
return ( return (
<Animated.View style={[styles.desktopSidebar, resizeAnimatedStyle]}> <Animated.View style={[styles.desktopSidebar, resizeAnimatedStyle]}>
{trafficLightPadding.top > 0 ? ( {trafficLightPadding.side === 'left' ? (
<View style={{ height: trafficLightPadding.top }} {...dragHandlers} /> <View style={{ height: trafficLightPadding.top }} {...dragHandlers} />
) : null} ) : null}
<View style={styles.sidebarHeader} {...dragHandlers}> <View style={styles.sidebarHeader} {...dragHandlers}>
@@ -886,7 +886,10 @@ function SplitPaneView({
style={[ style={[
styles.paneTabs, styles.paneTabs,
isFocusModeEnabled && isFocusModeEnabled &&
trafficLightPadding.left > 0 && { paddingLeft: trafficLightPadding.left }, trafficLightPadding.side && {
paddingLeft: trafficLightPadding.left,
paddingRight: trafficLightPadding.right,
},
]} ]}
> >
<WorkspaceDesktopTabsRow <WorkspaceDesktopTabsRow
+4
View File
@@ -19,6 +19,10 @@ export const MAX_CONTENT_WIDTH = 820;
export const DESKTOP_TRAFFIC_LIGHT_WIDTH = 78; export const DESKTOP_TRAFFIC_LIGHT_WIDTH = 78;
export const DESKTOP_TRAFFIC_LIGHT_HEIGHT = 45; 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) // Check if running in desktop app (any OS)
function isDesktopEnvironment(): boolean { function isDesktopEnvironment(): boolean {
if (Platform.OS !== "web") return false; if (Platform.OS !== "web") return false;
@@ -7,7 +7,6 @@ import { PaseoLogo } from "@/components/icons/paseo-logo";
import { SidebarMenuToggle } from "@/components/headers/menu-header"; import { SidebarMenuToggle } from "@/components/headers/menu-header";
import { useOpenProjectPicker } from "@/hooks/use-open-project-picker"; import { useOpenProjectPicker } from "@/hooks/use-open-project-picker";
import { usePanelStore } from "@/stores/panel-store"; import { usePanelStore } from "@/stores/panel-store";
import { getIsDesktopMac } from "@/constants/layout";
import { useDesktopDragHandlers, useTrafficLightPadding } from "@/utils/desktop-window"; import { useDesktopDragHandlers, useTrafficLightPadding } from "@/utils/desktop-window";
export function OpenProjectScreen({ serverId }: { serverId: string }) { export function OpenProjectScreen({ serverId }: { serverId: string }) {
@@ -19,8 +18,10 @@ export function OpenProjectScreen({ serverId }: { serverId: string }) {
const openProjectPicker = useOpenProjectPicker(serverId); const openProjectPicker = useOpenProjectPicker(serverId);
const isMobile = UnistylesRuntime.breakpoint === "xs" || UnistylesRuntime.breakpoint === "sm"; const isMobile = UnistylesRuntime.breakpoint === "xs" || UnistylesRuntime.breakpoint === "sm";
const needsTrafficLightInset = !isMobile && !desktopAgentListOpen && getIsDesktopMac(); const collapsedSidebarInset =
const trafficLightInset = needsTrafficLightInset ? trafficLightPadding.left : 0; !isMobile && !desktopAgentListOpen && trafficLightPadding.side
? trafficLightPadding
: { left: 0, right: 0 };
const dragHandlers = useDesktopDragHandlers(); const dragHandlers = useDesktopDragHandlers();
useEffect(() => { useEffect(() => {
@@ -31,7 +32,7 @@ export function OpenProjectScreen({ serverId }: { serverId: string }) {
return ( return (
<View style={styles.container} {...dragHandlers}> <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 /> <SidebarMenuToggle />
</View> </View>
<View style={styles.content}> <View style={styles.content}>
+20 -6
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 { Platform, type PointerEvent as RNPointerEvent, type ViewProps } from "react-native";
import { import {
getIsDesktopMac, getIsDesktopMac,
getIsDesktop,
DESKTOP_TRAFFIC_LIGHT_WIDTH, DESKTOP_TRAFFIC_LIGHT_WIDTH,
DESKTOP_TRAFFIC_LIGHT_HEIGHT, DESKTOP_TRAFFIC_LIGHT_HEIGHT,
DESKTOP_WINDOW_CONTROLS_WIDTH,
DESKTOP_WINDOW_CONTROLS_HEIGHT,
} from "@/constants/layout"; } from "@/constants/layout";
import { getDesktopWindow } from "@/desktop/electron/window"; import { getDesktopWindow } from "@/desktop/electron/window";
import { isDesktop } from "@/desktop/host"; import { isDesktop } from "@/desktop/host";
@@ -120,11 +123,11 @@ export function useDesktopDragHandlers(): DesktopDragViewProps {
}, [isActive]); }, [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); const [isFullscreen, setIsFullscreen] = useState(false);
useEffect(() => { useEffect(() => {
if (Platform.OS !== "web" || !getIsDesktopMac()) return; if (Platform.OS !== "web" || !getIsDesktop()) return;
let disposed = false; let disposed = false;
let cleanup: (() => void) | undefined; let cleanup: (() => void) | undefined;
@@ -175,12 +178,23 @@ export function useTrafficLightPadding(): { left: number; top: number } {
}; };
}, []); }, []);
if (!getIsDesktopMac() || isFullscreen) { if (!getIsDesktop() || isFullscreen) {
return { left: 0, top: 0 }; 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 { return {
left: DESKTOP_TRAFFIC_LIGHT_WIDTH, left: 0,
top: DESKTOP_TRAFFIC_LIGHT_HEIGHT, right: DESKTOP_WINDOW_CONTROLS_WIDTH,
top: DESKTOP_WINDOW_CONTROLS_HEIGHT,
side: 'right',
}; };
} }
+11 -2
View File
@@ -92,8 +92,17 @@ async function createMainWindow(): Promise<void> {
height: 800, height: 800,
show: false, show: false,
...(iconPath ? { icon: iconPath } : {}), ...(iconPath ? { icon: iconPath } : {}),
titleBarStyle: isMac ? "hidden" : "default", titleBarStyle: "hidden",
trafficLightPosition: isMac ? { x: 16, y: 14 } : undefined, ...(isMac
? { trafficLightPosition: { x: 16, y: 14 } }
: {
titleBarOverlay: {
color: "#18181c",
symbolColor: "#e4e4e7",
height: 48,
},
autoHideMenuBar: true,
}),
webPreferences: { webPreferences: {
preload: getPreloadPath(), preload: getPreloadPath(),
contextIsolation: true, contextIsolation: true,