/** * TitleBar Component * macOS: light application toolbar with native traffic lights overlaid by the * full-size hidden title bar. * Windows: drag region with custom minimize/maximize/close controls. * Linux: use native window chrome (no custom title bar). */ import { useState, useEffect } from 'react'; import { createPortal } from 'react-dom'; import { Copy, FolderOpen, Minus, PanelLeft, PanelLeftClose, Square, X } from 'lucide-react'; import { invokeIpc } from '@/lib/api-client'; import { cn } from '@/lib/utils'; import { useCodingWorkspaceStore } from '@/stores/coding-workspace'; import { useSettingsStore } from '@/stores/settings'; import logoWordmarkSource from '@/assets/makelore-wordmark-source.png'; import type { SidebarPeekSource } from './sidebar-peek'; type SidebarPeekChange = (open: boolean, source: SidebarPeekSource) => void; // Three 44px buttons plus the control container's 8px padding on each side. const WINDOWS_TITLEBAR_CONTROLS_WIDTH = 148; type TitleBarProps = { integrated?: boolean; workspaceLayout?: boolean; overlay?: boolean; pageTitle?: string; sidebarPeekOpen?: boolean; onSidebarPeekChange?: SidebarPeekChange; }; export function TitleBar({ integrated = false, workspaceLayout = false, overlay = false, pageTitle, sidebarPeekOpen = false, onSidebarPeekChange }: TitleBarProps) { const platform = window.electron?.platform; if (platform === 'darwin') { return ; } // Linux keeps the native frame/title bar for better IME compatibility. if (platform !== 'win32') { return null; } return ; } function ProductTitleBar({ integrated, workspaceLayout = false, overlay = false, pageTitle, children, windowControlsWidth = 0, nativeTrafficLights = false, sidebarPeekOpen = false, onSidebarPeekChange, }: { integrated: boolean; workspaceLayout?: boolean; overlay?: boolean; pageTitle?: string; children?: React.ReactNode; windowControlsWidth?: number; nativeTrafficLights?: boolean; sidebarPeekOpen?: boolean; onSidebarPeekChange?: SidebarPeekChange; }) { const sidebarCollapsed = useSettingsStore((state) => state.sidebarCollapsed); const activeProject = useCodingWorkspaceStore((state) => state.activeProject); const toolbarButtonClass = 'no-drag motion-press flex h-8 w-8 shrink-0 items-center justify-center rounded-md text-muted-foreground hover:bg-surface-subtle hover:text-foreground'; const setSidebarCollapsed = useSettingsStore((state) => state.setSidebarCollapsed); const toggleSidebar = () => setSidebarCollapsed(!sidebarCollapsed); const handleSidebarPeekEnter = (source: SidebarPeekSource) => { if (sidebarCollapsed) onSidebarPeekChange?.(true, source); }; const handleSidebarPeekLeave = (source: SidebarPeekSource) => { if (sidebarCollapsed) onSidebarPeekChange?.(false, source); }; // The project sidebar preview is rendered below the title bar, so mirror // its full width in the title bar for ordinary pages. The chat workspace // has an independent conversation rail and must keep its pinned columns. const sidebarPreviewTitlebarVisible = sidebarCollapsed && sidebarPeekOpen && !workspaceLayout && !overlay; const sidebarTitlebarHidden = sidebarCollapsed && !sidebarPreviewTitlebarVisible; const conversationSurfaceOffset = sidebarTitlebarHidden ? (nativeTrafficLights ? '-132px' : '-52px') : '0px'; const sidebarToggle = ( ); const sidebarTogglePortal = (
{sidebarToggle}
); const titlebarLogo = (
Makelore logo
); return (
{integrated ? ( <>
handleSidebarPeekEnter('titlebar') : undefined} onPointerLeave={sidebarPreviewTitlebarVisible ? () => handleSidebarPeekLeave('titlebar') : undefined} /> {typeof document !== 'undefined' ? createPortal(sidebarTogglePortal, document.body) : sidebarTogglePortal} ) : null} {integrated ? (
{workspaceLayout ? (
) : null} {pageTitle ? (

{pageTitle}

) : !workspaceLayout && !overlay ? (
) : null} {!overlay ? titlebarLogo : null}
{children}
) : (
{children}
)} {integrated && overlay ? titlebarLogo : null}
); } function WindowsTitleBar({ integrated, workspaceLayout, overlay, pageTitle, sidebarPeekOpen, onSidebarPeekChange }: Omit & { integrated: boolean }) { const [maximized, setMaximized] = useState(false); useEffect(() => { // Check initial state invokeIpc('window:isMaximized').then((val) => { setMaximized(val as boolean); }); }, []); const handleMinimize = () => { invokeIpc('window:minimize'); }; const handleMaximize = () => { invokeIpc('window:maximize').then(() => { invokeIpc('window:isMaximized').then((val) => { setMaximized(val as boolean); }); }); }; const handleClose = () => { invokeIpc('window:close'); }; return (
); }