mirror of
https://github.com/Caho1/open-claude-ui.git
synced 2026-11-07 10:26:02 -10:00
99 lines
3.1 KiB
TypeScript
99 lines
3.1 KiB
TypeScript
/**
|
|
* 维度四:macOS 桌面深度融合与 Web 自适应顶栏
|
|
* 逆向还原自 Claude Desktop: shared-19-DmuztgN-.js / shared-21-CGGmSsMS.js
|
|
*
|
|
* 核心特性:
|
|
* 1. 74px 红绿灯物理避让占位槽 (ew = 74)
|
|
* 2. 逆缩放补偿宽度 Math.round(74 / Math.min(zoomFactor, 1))
|
|
* 3. macOS 全屏感知与动态 safe-area 回缩
|
|
* 4. 45px 全局独立拖拽带与 .draggable-none 交互隔离
|
|
*/
|
|
|
|
import React, { useEffect, useState } from 'react';
|
|
|
|
interface ClaudeDesktopHeaderProps {
|
|
title?: string;
|
|
children?: React.ReactNode;
|
|
}
|
|
|
|
export const ClaudeDesktopHeader: React.FC<ClaudeDesktopHeaderProps> = ({
|
|
title,
|
|
children
|
|
}) => {
|
|
const [isMac, setIsMac] = useState(false);
|
|
const [isFullscreen, setIsFullscreen] = useState(false);
|
|
const [zoomFactor, setZoomFactor] = useState(1);
|
|
|
|
useEffect(() => {
|
|
const isDarwin = navigator.userAgent.includes('Mac');
|
|
setIsMac(isDarwin);
|
|
|
|
// 监听 Electron 宿主暴露的窗口状态
|
|
const electronAPI = (window as any).electronAPI;
|
|
if (electronAPI) {
|
|
electronAPI.getFullscreen?.().then(setIsFullscreen);
|
|
const unsubFs = electronAPI.onFullscreenChanged?.(setIsFullscreen);
|
|
const unsubZoom = electronAPI.onZoomFactorChanged?.(setZoomFactor);
|
|
return () => {
|
|
unsubFs?.();
|
|
unsubZoom?.();
|
|
};
|
|
}
|
|
}, []);
|
|
|
|
const needsTrafficLight = isMac && !isFullscreen;
|
|
// 74px 逆缩放补偿算法
|
|
const trafficLightSpacerWidth = needsTrafficLight
|
|
? Math.round(74 / Math.min(zoomFactor, 1))
|
|
: 0;
|
|
|
|
return (
|
|
<>
|
|
{/* 1. 全局绝对定位拖拽带(仅响应窗口拖动,对所有鼠标交互穿透) */}
|
|
<div
|
|
aria-hidden="true"
|
|
data-cds-window-drag-strip=""
|
|
className="fixed inset-x-0 top-0 z-40 pointer-events-none"
|
|
style={{
|
|
height: needsTrafficLight ? '45px' : '0px',
|
|
WebkitAppRegion: 'drag' as any
|
|
}}
|
|
/>
|
|
|
|
{/* 2. 顶栏容器 */}
|
|
<header
|
|
className="relative z-30 flex h-[45px] w-full items-center border-b border-[var(--cds-border)] bg-[var(--cds-surface-1)] px-4 transition-[padding] duration-150 select-none"
|
|
style={{
|
|
WebkitAppRegion: 'drag' as any
|
|
}}
|
|
>
|
|
{/* 红绿灯规避占位槽 */}
|
|
{trafficLightSpacerWidth > 0 && (
|
|
<div
|
|
aria-hidden="true"
|
|
className="shrink-0 transition-[width] duration-150"
|
|
style={{ width: `${trafficLightSpacerWidth}px` }}
|
|
/>
|
|
)}
|
|
|
|
{/* 标题显示区(带拖拽属性) */}
|
|
<div className="flex min-w-0 flex-1 items-center gap-2">
|
|
{title && (
|
|
<span className="truncate text-caption font-medium text-[var(--cds-text-secondary)]">
|
|
{title}
|
|
</span>
|
|
)}
|
|
</div>
|
|
|
|
{/* 交互控件区:必须严格包裹 draggable-none,防御误触拖动 */}
|
|
<div
|
|
className="draggable-none flex shrink-0 items-center gap-2"
|
|
style={{ WebkitAppRegion: 'no-drag' as any }}
|
|
>
|
|
{children}
|
|
</div>
|
|
</header>
|
|
</>
|
|
);
|
|
};
|