feat(style): refactor layout, remove Header & Improve gateway readiness checks (#12)

This commit is contained in:
DigHuang
2026-02-09 01:00:27 -08:00
committed by GitHub
Unverified
parent 86ddd843c4
commit 05b5874832
14 changed files with 217 additions and 132 deletions

View File

@@ -1,61 +0,0 @@
/**
* Header Component
* Top navigation bar with page title and page-specific controls.
* On the Chat page, shows session selector, refresh, thinking toggle, and new session.
*/
import { useLocation } from 'react-router-dom';
import { Terminal } from 'lucide-react';
import { ChatToolbar } from '@/pages/Chat/ChatToolbar';
import { Button } from '@/components/ui/button';
// Page titles mapping
const pageTitles: Record<string, string> = {
'/': 'Chat',
'/dashboard': 'Dashboard',
'/channels': 'Channels',
'/skills': 'Skills',
'/cron': 'Cron Tasks',
'/settings': 'Settings',
};
export function Header() {
const location = useLocation();
const currentTitle = pageTitles[location.pathname] || 'ClawX';
const isChatPage = location.pathname === '/';
const isDashboard = location.pathname === '/dashboard';
const handleOpenDevConsole = async () => {
try {
const result = await window.electron.ipcRenderer.invoke('gateway:getControlUiUrl') as { success: boolean; url?: string; error?: string };
if (result.success && result.url) {
window.electron.openExternal(result.url);
} else {
console.error('Failed to get Dev Console URL:', result.error);
}
} catch (err) {
console.error('Error opening Dev Console:', err);
}
};
return (
<header className="flex h-14 items-center justify-between border-b bg-background px-6">
<h2 className="text-lg font-semibold">{currentTitle}</h2>
{/* Chat-specific controls */}
{isChatPage && <ChatToolbar />}
{/* Dashboard specific controls - Dev Console Button */}
{isDashboard && (
<Button
variant="ghost"
size="sm"
className="h-7 gap-1.5 rounded-full border border-neutral-200 px-3 text-xs font-normal text-neutral-500 hover:bg-neutral-50 hover:text-neutral-700 dark:border-neutral-800 dark:text-neutral-400 dark:hover:bg-neutral-900 dark:hover:text-neutral-200"
onClick={handleOpenDevConsole}
>
<Terminal className="h-3.5 w-3.5" />
Gateway
</Button>
)}
</header>
);
}

View File

@@ -1,32 +1,20 @@
/**
* Main Layout Component
* Provides the primary app layout with sidebar and content area
* TitleBar at top, then sidebar + content below.
*/
import { Outlet } from 'react-router-dom';
import { Sidebar } from './Sidebar';
import { Header } from './Header';
import { useSettingsStore } from '@/stores/settings';
import { cn } from '@/lib/utils';
import { TitleBar } from './TitleBar';
export function MainLayout() {
const sidebarCollapsed = useSettingsStore((state) => state.sidebarCollapsed);
return (
<div className="flex h-screen overflow-hidden bg-background">
{/* Sidebar */}
<Sidebar />
{/* Main Content Area */}
<div
className={cn(
'flex flex-1 flex-col overflow-hidden transition-all duration-300',
sidebarCollapsed ? 'ml-16' : 'ml-64'
)}
>
{/* Header */}
<Header />
{/* Page Content */}
<div className="flex h-screen flex-col overflow-hidden bg-background">
{/* Title bar: drag region on macOS, icon + controls on Windows */}
<TitleBar />
{/* Below the title bar: sidebar + content */}
<div className="flex flex-1 overflow-hidden">
<Sidebar />
<main className="flex-1 overflow-auto p-6">
<Outlet />
</main>

View File

@@ -1,6 +1,7 @@
/**
* Sidebar Component
* Navigation sidebar with menu items
* Navigation sidebar with menu items.
* No longer fixed - sits inside the flex layout below the title bar.
*/
import { NavLink } from 'react-router-dom';
import {
@@ -17,11 +18,9 @@ import {
} from 'lucide-react';
import { cn } from '@/lib/utils';
import { useSettingsStore } from '@/stores/settings';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
interface NavItemProps {
to: string;
icon: React.ReactNode;
@@ -64,11 +63,11 @@ export function Sidebar() {
const sidebarCollapsed = useSettingsStore((state) => state.sidebarCollapsed);
const setSidebarCollapsed = useSettingsStore((state) => state.setSidebarCollapsed);
const devModeUnlocked = useSettingsStore((state) => state.devModeUnlocked);
// Open developer console
const openDevConsole = () => {
window.electron.openExternal('http://localhost:18789');
};
const navItems = [
{ to: '/', icon: <MessageSquare className="h-5 w-5" />, label: 'Chat' },
{ to: '/cron', icon: <Clock className="h-5 w-5" />, label: 'Cron Tasks' },
@@ -77,25 +76,16 @@ export function Sidebar() {
{ to: '/dashboard', icon: <Home className="h-5 w-5" />, label: 'Dashboard' },
{ to: '/settings', icon: <Settings className="h-5 w-5" />, label: 'Settings' },
];
return (
<aside
className={cn(
'fixed left-0 top-0 z-40 flex h-screen flex-col border-r bg-background transition-all duration-300',
'flex shrink-0 flex-col border-r bg-background transition-all duration-300',
sidebarCollapsed ? 'w-16' : 'w-64'
)}
>
{/* Header with drag region for macOS */}
<div className="drag-region flex h-14 items-center border-b px-4">
{/* macOS traffic light spacing */}
<div className="w-16" />
{!sidebarCollapsed && (
<h1 className="no-drag text-xl font-bold">ClawX</h1>
)}
</div>
{/* Navigation */}
<nav className="flex-1 space-y-1 p-2">
<nav className="flex-1 space-y-1 overflow-auto p-2">
{navItems.map((item) => (
<NavItem
key={item.to}
@@ -104,10 +94,9 @@ export function Sidebar() {
/>
))}
</nav>
{/* Footer */}
<div className="p-2 space-y-2">
{/* Developer Mode Button */}
{devModeUnlocked && !sidebarCollapsed && (
<Button
variant="ghost"
@@ -121,8 +110,6 @@ export function Sidebar() {
</Button>
)}
{/* Collapse Toggle */}
<Button
variant="ghost"
size="icon"

View File

@@ -0,0 +1,83 @@
/**
* TitleBar Component
* macOS: empty drag region (native traffic lights handled by hiddenInset).
* Windows/Linux: icon + "ClawX" on left, minimize/maximize/close on right.
*/
import { useState, useEffect } from 'react';
import { Minus, Square, X, Copy } from 'lucide-react';
import logoSvg from '@/assets/logo.svg';
const isMac = window.electron?.platform === 'darwin';
export function TitleBar() {
if (isMac) {
// macOS: just a drag region, traffic lights are native
return <div className="drag-region h-10 shrink-0 border-b bg-background" />;
}
return <WindowsTitleBar />;
}
function WindowsTitleBar() {
const [maximized, setMaximized] = useState(false);
useEffect(() => {
// Check initial state
window.electron.ipcRenderer.invoke('window:isMaximized').then((val) => {
setMaximized(val as boolean);
});
}, []);
const handleMinimize = () => {
window.electron.ipcRenderer.invoke('window:minimize');
};
const handleMaximize = () => {
window.electron.ipcRenderer.invoke('window:maximize').then(() => {
window.electron.ipcRenderer.invoke('window:isMaximized').then((val) => {
setMaximized(val as boolean);
});
});
};
const handleClose = () => {
window.electron.ipcRenderer.invoke('window:close');
};
return (
<div className="drag-region flex h-10 shrink-0 items-center justify-between border-b bg-background">
{/* Left: Icon + App Name */}
<div className="no-drag flex items-center gap-2 pl-3">
<img src={logoSvg} alt="ClawX" className="h-5 w-auto" />
<span className="text-xs font-medium text-muted-foreground select-none">
ClawX
</span>
</div>
{/* Right: Window Controls */}
<div className="no-drag flex h-full">
<button
onClick={handleMinimize}
className="flex h-full w-11 items-center justify-center text-muted-foreground hover:bg-accent transition-colors"
title="Minimize"
>
<Minus className="h-4 w-4" />
</button>
<button
onClick={handleMaximize}
className="flex h-full w-11 items-center justify-center text-muted-foreground hover:bg-accent transition-colors"
title={maximized ? 'Restore' : 'Maximize'}
>
{maximized ? <Copy className="h-3.5 w-3.5" /> : <Square className="h-3.5 w-3.5" />}
</button>
<button
onClick={handleClose}
className="flex h-full w-11 items-center justify-center text-muted-foreground hover:bg-red-500 hover:text-white transition-colors"
title="Close"
>
<X className="h-4 w-4" />
</button>
</div>
</div>
);
}