diff --git a/packages/ui/src/components/layout/Header.tsx b/packages/ui/src/components/layout/Header.tsx index a4ceb5ae..71a8827b 100644 --- a/packages/ui/src/components/layout/Header.tsx +++ b/packages/ui/src/components/layout/Header.tsx @@ -5,7 +5,7 @@ import { TooltipTrigger, } from '@/components/ui/tooltip'; -import { RiChat4Line, RiCodeLine, RiCommandLine, RiFolder6Line, RiGitBranchLine, RiLayoutLeftLine, RiPlayListAddLine, RiQuestionLine, RiSettings3Line, RiTerminalBoxLine, type RemixiconComponentType } from '@remixicon/react'; +import { RiArrowLeftSLine, RiChat4Line, RiCodeLine, RiCommandLine, RiFolder6Line, RiGitBranchLine, RiLayoutLeftLine, RiPlayListAddLine, RiQuestionLine, RiSettings3Line, RiTerminalBoxLine, type RemixiconComponentType } from '@remixicon/react'; import { useUIStore, type MainTab } from '@/stores/useUIStore'; import { useUpdateStore } from '@/stores/useUpdateStore'; import { useConfigStore } from '@/stores/useConfigStore'; @@ -24,71 +24,12 @@ interface TabConfig { showDot?: boolean; } -export const FixedSessionsButton: React.FC = () => { - const setSessionSwitcherOpen = useUIStore((state) => state.setSessionSwitcherOpen); - const toggleSidebar = useUIStore((state) => state.toggleSidebar); - const { isMobile } = useDeviceInfo(); - - const [isDesktopApp, setIsDesktopApp] = React.useState(() => { - if (typeof window === 'undefined') { - return false; - } - return typeof (window as typeof window & { opencodeDesktop?: unknown }).opencodeDesktop !== 'undefined'; - }); - - const isMacPlatform = React.useMemo(() => { - if (typeof navigator === 'undefined') { - return false; - } - return /Macintosh|Mac OS X/.test(navigator.userAgent || ''); - }, []); - - React.useEffect(() => { - if (typeof window === 'undefined') { - return; - } - const detected = typeof (window as typeof window & { opencodeDesktop?: unknown }).opencodeDesktop !== 'undefined'; - setIsDesktopApp(detected); - }, []); - - const handleOpenSessionSwitcher = React.useCallback(() => { - if (isMobile) { - setSessionSwitcherOpen(true); - } else { - toggleSidebar(); - } - }, [isMobile, setSessionSwitcherOpen, toggleSidebar]); - - const headerIconButtonClass = 'app-region-no-drag inline-flex h-9 w-9 items-center justify-center rounded-md gap-2 p-2 typography-ui-label font-medium text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary disabled:pointer-events-none disabled:opacity-50 hover:text-foreground hover:bg-secondary/50 transition-colors'; - - if (isMobile || !isDesktopApp || !isMacPlatform) { - return null; - } - - return ( -
- -
- ); -}; - export const Header: React.FC = () => { const setSessionSwitcherOpen = useUIStore((state) => state.setSessionSwitcherOpen); const toggleSidebar = useUIStore((state) => state.toggleSidebar); const setSettingsDialogOpen = useUIStore((state) => state.setSettingsDialogOpen); const toggleCommandPalette = useUIStore((state) => state.toggleCommandPalette); const toggleHelpDialog = useUIStore((state) => state.toggleHelpDialog); - const isSidebarOpen = useUIStore((state) => state.isSidebarOpen); const activeMainTab = useUIStore((state) => state.activeMainTab); const setActiveMainTab = useUIStore((state) => state.setActiveMainTab); @@ -171,10 +112,11 @@ export const Header: React.FC = () => { const desktopPaddingClass = React.useMemo(() => { if (isDesktopApp && isMacPlatform) { - return isSidebarOpen ? 'pl-2' : 'pl-[8.0rem]'; + // Always reserve space for Mac traffic lights since header is always on top + return 'pl-[5.75rem]'; } return 'pl-3'; - }, [isDesktopApp, isMacPlatform, isSidebarOpen]); + }, [isDesktopApp, isMacPlatform]); const updateHeaderHeight = React.useCallback(() => { if (typeof document === 'undefined') { @@ -344,18 +286,14 @@ export const Header: React.FC = () => { role="tablist" aria-label="Main navigation" > - {!(isDesktopApp && isMacPlatform) && ( - <> - - - )} +
{tabs.map((tab) => renderTab(tab))} @@ -404,14 +342,25 @@ export const Header: React.FC = () => { const renderMobile = () => (
- - {contextUsage && contextUsage.totalTokens > 0 && activeMainTab === 'chat' && ( + {/* Show back button when sessions sidebar is open, otherwise show sessions toggle */} + {isSessionSwitcherOpen ? ( + + ) : ( + + )} + {!isSessionSwitcherOpen && contextUsage && contextUsage.totalTokens > 0 && activeMainTab === 'chat' && ( { isMobile={true} /> )} + {isSessionSwitcherOpen && ( + Sessions + )}
-
+ {/* Hide tabs and right-side buttons when sessions sidebar is open */} + {!isSessionSwitcherOpen && ( +
+
+ {tabs.map((tab) => { + const isActive = activeMainTab === tab.id; + const Icon = tab.icon; + return ( + + + + + +

{tab.label}

+
+
+ ); + })} +
-
+ - {tabs.map((tab) => { - const isActive = activeMainTab === tab.id; - const Icon = tab.icon; - return ( - - - - - -

{tab.label}

-
-
- ); - })} + + + + + +

{updateAvailable ? 'Settings (Update available)' : 'Settings'}

+
+
- - - - - - - - -

{updateAvailable ? 'Settings (Update available)' : 'Settings'}

-
-
-
+ )}
); diff --git a/packages/ui/src/components/layout/MainLayout.tsx b/packages/ui/src/components/layout/MainLayout.tsx index adf750dc..e8f83392 100644 --- a/packages/ui/src/components/layout/MainLayout.tsx +++ b/packages/ui/src/components/layout/MainLayout.tsx @@ -1,12 +1,11 @@ import React from 'react'; -import { Header, FixedSessionsButton } from './Header'; +import { Header } from './Header'; import { Sidebar } from './Sidebar'; import { ErrorBoundary } from '../ui/ErrorBoundary'; import { CommandPalette } from '../ui/CommandPalette'; import { HelpDialog } from '../ui/HelpDialog'; import { SessionSidebar } from '@/components/session/SessionSidebar'; import { SessionDialogs } from '@/components/session/SessionDialogs'; -import { MobileOverlayPanel } from '@/components/ui/MobileOverlayPanel'; import { DiffWorkerProvider } from '@/contexts/DiffWorkerProvider'; import { MultiRunLauncher } from '@/components/multirun'; @@ -24,7 +23,6 @@ export const MainLayout: React.FC = () => { activeMainTab, setIsMobile, isSessionSwitcherOpen, - setSessionSwitcherOpen, isSettingsDialogOpen, setSettingsDialogOpen, isMultiRunLauncherOpen, @@ -331,35 +329,34 @@ export const MainLayout: React.FC = () => { {isMobile ? ( <> - {/* Mobile: Header + content + overlays */} + {/* Mobile: Header + content with drill-down pattern */} {!(isSettingsDialogOpen || isMultiRunLauncherOpen) &&
}
-
-
- + {/* Mobile drill-down: show sessions sidebar OR main content */} + {isSessionSwitcherOpen ? ( +
+
- {secondaryView && ( -
- {secondaryView} + ) : ( +
+
+
- )} -
+ {secondaryView && ( +
+ {secondaryView} +
+ )} +
+ )}
- setSessionSwitcherOpen(false)} - title="Sessions" - > - - - {/* Mobile multi-run launcher: full screen */} {isMultiRunLauncherOpen && (
@@ -382,18 +379,15 @@ export const MainLayout: React.FC = () => { ) : ( <> - {!isSettingsActive && ( - - - - )} - - {/* Main content area */} + {/* Desktop: Header always on top, then Sidebar + Content below */}
- {/* Normal view: Header + content */} + {/* Normal view: Header above Sidebar + content (like SettingsView) */}
-
+
+ + +
@@ -430,8 +424,6 @@ export const MainLayout: React.FC = () => { )} - {/* Hide fixed sessions button when settings is open */} - {!isSettingsActive && }
); diff --git a/packages/ui/src/components/layout/Sidebar.tsx b/packages/ui/src/components/layout/Sidebar.tsx index 96846f67..9ebf7c9a 100644 --- a/packages/ui/src/components/layout/Sidebar.tsx +++ b/packages/ui/src/components/layout/Sidebar.tsx @@ -11,7 +11,6 @@ import { Tooltip, TooltipContent, TooltipTrigger } from '../ui/tooltip'; export const SIDEBAR_CONTENT_WIDTH = 264; const SIDEBAR_MIN_WIDTH = 200; const SIDEBAR_MAX_WIDTH = 500; -const MAC_TITLEBAR_SAFE_AREA = 40; const CHECK_FOR_UPDATES_EVENT = 'openchamber:check-for-updates'; interface SidebarProps { @@ -40,12 +39,7 @@ export const Sidebar: React.FC = ({ isOpen, isMobile, children }) return typeof (window as typeof window & { opencodeDesktop?: unknown }).opencodeDesktop !== 'undefined'; }); - const isMacPlatform = React.useMemo(() => { - if (typeof navigator === 'undefined') { - return false; - } - return /Macintosh|Mac OS X/.test(navigator.userAgent || ''); - }, []); + React.useEffect(() => { if (typeof window === 'undefined') { @@ -127,23 +121,6 @@ export const Sidebar: React.FC = ({ isOpen, isMobile, children }) } }, [isMobile, isResizing]); - const handleTitlebarDragStart = React.useCallback(async (e: React.MouseEvent) => { - - if (e.button !== 0) { - return; - } - - if (isDesktopApp) { - try { - const { getCurrentWindow } = await import('@tauri-apps/api/window'); - const window = getCurrentWindow(); - await window.startDragging(); - } catch (error) { - console.error('Failed to start window dragging from sidebar:', error); - } - } - }, [isDesktopApp]); - if (isMobile) { return null; @@ -153,7 +130,6 @@ export const Sidebar: React.FC = ({ isOpen, isMobile, children }) SIDEBAR_MAX_WIDTH, Math.max(SIDEBAR_MIN_WIDTH, sidebarWidth || SIDEBAR_CONTENT_WIDTH) ) : 0; - const shouldRenderTitlebarSpacer = isDesktopApp && isMacPlatform; const handlePointerDown = (event: React.PointerEvent) => { if (!isOpen) { @@ -203,14 +179,6 @@ export const Sidebar: React.FC = ({ isOpen, isMobile, children }) style={{ width: `${appliedWidth}px`, overflowX: 'hidden' }} aria-hidden={!isOpen} > - {shouldRenderTitlebarSpacer && ( -
- )}
{children}