feat(desktop): enable macOS version awareness for UI adjustments
- Fetch macOS major version from Rust to determine layout - Adjust header padding and height for macOS versions
This commit is contained in:
@@ -755,16 +755,20 @@ const ChatMessage: React.FC<ChatMessageProps> = ({
|
||||
onContentChange?.('structural');
|
||||
}, [isUser, onContentChange]);
|
||||
|
||||
const setImagePreviewOpen = useUIStore((state) => state.setImagePreviewOpen);
|
||||
|
||||
const handleShowPopup = React.useCallback((content: ToolPopupContent) => {
|
||||
|
||||
if (content.image) {
|
||||
setPopupContent(content);
|
||||
setImagePreviewOpen(true);
|
||||
}
|
||||
}, []);
|
||||
}, [setImagePreviewOpen]);
|
||||
|
||||
const handlePopupChange = React.useCallback((open: boolean) => {
|
||||
setPopupContent((prev) => ({ ...prev, open }));
|
||||
}, []);
|
||||
setImagePreviewOpen(open);
|
||||
}, [setImagePreviewOpen]);
|
||||
|
||||
const isAnimationSettled = Boolean(getMessageInfoProp(message.info, 'animationSettled'));
|
||||
const isStreamingPhase = streamPhase === 'streaming';
|
||||
|
||||
@@ -108,15 +108,28 @@ export const Header: React.FC = () => {
|
||||
}, []);
|
||||
|
||||
const macosMajorVersion = React.useMemo(() => {
|
||||
if (typeof window === 'undefined') {
|
||||
return null;
|
||||
}
|
||||
// Use Tauri-provided version if available (accurate), otherwise fall back to UA parsing
|
||||
const desktopApi = (window as typeof window & { opencodeDesktop?: { macosMajorVersion?: number | null } }).opencodeDesktop;
|
||||
if (desktopApi?.macosMajorVersion != null) {
|
||||
return desktopApi.macosMajorVersion;
|
||||
}
|
||||
// Fallback: WebKit reports "Mac OS X 10_15_7" format where 10 is legacy prefix
|
||||
if (typeof navigator === 'undefined') {
|
||||
return null;
|
||||
}
|
||||
const match = (navigator.userAgent || '').match(/Mac OS X (\d+)[._]/);
|
||||
const match = (navigator.userAgent || '').match(/Mac OS X (\d+)[._](\d+)/);
|
||||
if (!match) {
|
||||
return null;
|
||||
}
|
||||
const parsed = Number.parseInt(match[1], 10);
|
||||
return Number.isNaN(parsed) ? null : parsed;
|
||||
const first = Number.parseInt(match[1], 10);
|
||||
const second = Number.parseInt(match[2], 10);
|
||||
if (Number.isNaN(first)) {
|
||||
return null;
|
||||
}
|
||||
return first === 10 ? second : first;
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -296,19 +309,22 @@ export const Header: React.FC = () => {
|
||||
const desktopPaddingClass = React.useMemo(() => {
|
||||
if (isDesktopApp && isMacPlatform) {
|
||||
// Always reserve space for Mac traffic lights since header is always on top
|
||||
return 'pl-[5.125rem]';
|
||||
return 'pl-[5.5rem]';
|
||||
}
|
||||
return 'pl-3';
|
||||
}, [isDesktopApp, isMacPlatform]);
|
||||
|
||||
const macosHeaderSizeClass = React.useMemo(() => {
|
||||
if (!isDesktopApp || !isMacPlatform) {
|
||||
if (!isDesktopApp || !isMacPlatform || macosMajorVersion === null) {
|
||||
return '';
|
||||
}
|
||||
if (macosMajorVersion === null || macosMajorVersion > 15) {
|
||||
return '';
|
||||
if (macosMajorVersion >= 26) {
|
||||
return 'h-12';
|
||||
}
|
||||
return 'h-14';
|
||||
if (macosMajorVersion <= 15) {
|
||||
return 'h-14';
|
||||
}
|
||||
return '';
|
||||
}, [isDesktopApp, isMacPlatform, macosMajorVersion]);
|
||||
|
||||
const updateHeaderHeight = React.useCallback(() => {
|
||||
@@ -351,7 +367,7 @@ export const Header: React.FC = () => {
|
||||
|
||||
useEffect(() => {
|
||||
updateHeaderHeight();
|
||||
}, [updateHeaderHeight, isMobile]);
|
||||
}, [updateHeaderHeight, isMobile, macosHeaderSizeClass]);
|
||||
|
||||
const handleDragStart = React.useCallback(async (e: React.MouseEvent) => {
|
||||
if ((e.target as HTMLElement).closest('button, a, input, select, textarea')) {
|
||||
|
||||
@@ -116,7 +116,7 @@ export const MultiRunLauncher: React.FC<MultiRunLauncherProps> = ({
|
||||
const desktopHeaderPaddingClass = React.useMemo(() => {
|
||||
if (isDesktopApp && isMacPlatform) {
|
||||
// Match main app header: reserve space for Mac traffic lights.
|
||||
return 'pl-[5.125rem]';
|
||||
return 'pl-[5.5rem]';
|
||||
}
|
||||
return 'pl-3';
|
||||
}, [isDesktopApp, isMacPlatform]);
|
||||
@@ -139,6 +139,20 @@ export const MultiRunLauncher: React.FC<MultiRunLauncherProps> = ({
|
||||
}
|
||||
}, [isDesktopApp]);
|
||||
|
||||
// Handle ESC key to dismiss
|
||||
React.useEffect(() => {
|
||||
if (!onCancel) return;
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if (e.key === 'Escape') {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
onCancel();
|
||||
}
|
||||
};
|
||||
window.addEventListener('keydown', handleKeyDown, true);
|
||||
return () => window.removeEventListener('keydown', handleKeyDown, true);
|
||||
}, [onCancel]);
|
||||
|
||||
// Use the BranchSelector hook for branch state management
|
||||
const [worktreeBaseBranch, setWorktreeBaseBranch] = React.useState<string>('HEAD');
|
||||
const { isLoading: isLoadingWorktreeBaseBranches, isGitRepository } = useBranchOptions(currentDirectory);
|
||||
@@ -306,31 +320,27 @@ export const MultiRunLauncher: React.FC<MultiRunLauncherProps> = ({
|
||||
<header
|
||||
onMouseDown={handleDragStart}
|
||||
className={cn(
|
||||
'flex h-12 items-center justify-between border-b app-region-drag select-none',
|
||||
'relative flex h-12 items-center justify-center border-b app-region-drag select-none',
|
||||
desktopHeaderPaddingClass
|
||||
)}
|
||||
style={{ borderColor: 'var(--interactive-border)' }}
|
||||
>
|
||||
<div
|
||||
className="flex items-center gap-3"
|
||||
>
|
||||
<h1 className="typography-ui-label font-medium">New Multi-Run</h1>
|
||||
</div>
|
||||
<h1 className="typography-ui-label font-medium">New Multi-Run</h1>
|
||||
{onCancel && (
|
||||
<div className="flex items-center pr-3">
|
||||
<div className="absolute right-0 flex items-center pr-3">
|
||||
<Tooltip delayDuration={500}>
|
||||
<TooltipTrigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onCancel}
|
||||
aria-label="Close"
|
||||
aria-label="Close (Esc)"
|
||||
className="inline-flex h-9 w-9 items-center justify-center p-2 text-muted-foreground hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary app-region-no-drag"
|
||||
>
|
||||
<RiCloseLine className="h-5 w-5" />
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>Close</p>
|
||||
<p>Close (Esc)</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user