Makelore 2.0 initial clean snapshot
This commit is contained in:
108
src/components/layout/TitleBar.tsx
Normal file
108
src/components/layout/TitleBar.tsx
Normal file
@@ -0,0 +1,108 @@
|
||||
/**
|
||||
* TitleBar Component
|
||||
* macOS: empty drag region (native traffic lights handled by hiddenInset).
|
||||
* Windows: drag region with custom minimize/maximize/close controls.
|
||||
* Linux: use native window chrome (no custom title bar).
|
||||
*/
|
||||
import { useState, useEffect } from 'react';
|
||||
import { Minus, Square, X, Copy } from 'lucide-react';
|
||||
import { invokeIpc } from '@/lib/api-client';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { useSettingsStore } from '@/stores/settings';
|
||||
|
||||
export function TitleBar({ integrated = false }: { integrated?: boolean }) {
|
||||
const platform = window.electron?.platform;
|
||||
|
||||
if (platform === 'darwin') {
|
||||
return <ProductTitleBar integrated={integrated} />;
|
||||
}
|
||||
|
||||
// Linux keeps the native frame/title bar for better IME compatibility.
|
||||
if (platform !== 'win32') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <WindowsTitleBar integrated={integrated} />;
|
||||
}
|
||||
|
||||
function ProductTitleBar({ integrated, children }: { integrated: boolean; children?: React.ReactNode }) {
|
||||
const sidebarCollapsed = useSettingsStore((state) => state.sidebarCollapsed);
|
||||
|
||||
return (
|
||||
<div
|
||||
data-testid="product-titlebar"
|
||||
className={cn(
|
||||
'drag-region flex h-10 shrink-0 bg-[#FFFFFF] text-[#26384D]',
|
||||
integrated && 'bg-[#FFFFFF]',
|
||||
)}
|
||||
>
|
||||
{integrated ? (
|
||||
<div
|
||||
data-testid="titlebar-sidebar-surface"
|
||||
className={cn(
|
||||
'h-full shrink-0 border-r-4 border-[#26384D] bg-[#F6F8FA] transition-[width] duration-300',
|
||||
sidebarCollapsed ? 'w-16' : 'w-72',
|
||||
)}
|
||||
/>
|
||||
) : null}
|
||||
<div className="flex min-w-0 flex-1 items-center justify-end bg-[#FFFFFF]">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function WindowsTitleBar({ integrated }: { 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 (
|
||||
<ProductTitleBar integrated={integrated}>
|
||||
<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>
|
||||
</ProductTitleBar>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user