mirror of
https://github.com/ValueCell-ai/ClawX.git
synced 2026-08-14 00:48:10 +00:00
fix: keep macOS artifact preview panel clickable above drag region (#1067)
This commit is contained in:
@@ -17,11 +17,11 @@
|
||||
* toolbar buttons, "View file changes →" links) can drive it.
|
||||
*/
|
||||
import { useLayoutEffect, useMemo, useRef } from 'react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { Eye, FileEdit, FolderOpen, FolderTree, X } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { toast } from 'sonner';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { supportsRichDocumentPreview, type GeneratedFile } from '@/lib/generated-files';
|
||||
import { invokeIpc } from '@/lib/api-client';
|
||||
import type { AgentSummary } from '@/types/agent';
|
||||
@@ -44,6 +44,7 @@ export interface ArtifactPanelProps {
|
||||
|
||||
export function ArtifactPanel({ files, agent, runStartedAt, refreshSignal }: ArtifactPanelProps) {
|
||||
const { t } = useTranslation('chat');
|
||||
const isMac = window.electron?.platform === 'darwin';
|
||||
const tab = useArtifactPanel((s) => s.tab);
|
||||
const setTab = useArtifactPanel((s) => s.setTab);
|
||||
const focusedFile = useArtifactPanel((s) => s.focusedFile);
|
||||
@@ -61,9 +62,16 @@ export function ArtifactPanel({ files, agent, runStartedAt, refreshSignal }: Art
|
||||
};
|
||||
|
||||
return (
|
||||
<div data-testid="artifact-panel" className="flex h-full min-h-0 flex-col bg-background">
|
||||
<div data-testid="artifact-panel" className={cn('flex h-full min-h-0 flex-col bg-background', isMac && 'no-drag')}>
|
||||
<div className="relative z-30 flex shrink-0 items-center justify-between gap-2 border-b border-black/5 bg-background px-3 py-2 dark:border-white/10">
|
||||
<div className="flex min-w-0 items-center gap-1">
|
||||
{isMac && (
|
||||
<div
|
||||
data-testid="artifact-panel-drag-region"
|
||||
className="drag-region absolute inset-0 z-0"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
)}
|
||||
<div className={cn('flex min-w-0 items-center gap-1', isMac && 'no-drag relative z-10')}>
|
||||
{richFocusedFile ? (
|
||||
<PanelTabButton
|
||||
testId="artifact-panel-action-open-folder"
|
||||
@@ -102,7 +110,7 @@ export function ArtifactPanel({ files, agent, runStartedAt, refreshSignal }: Art
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-7 w-7 shrink-0"
|
||||
className={cn('h-7 w-7 shrink-0', isMac && 'no-drag relative z-10')}
|
||||
onClick={close}
|
||||
aria-label={t('filePreview.actions.close', 'Close')}
|
||||
>
|
||||
@@ -110,7 +118,7 @@ export function ArtifactPanel({ files, agent, runStartedAt, refreshSignal }: Art
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="relative z-0 min-h-0 flex-1 overflow-hidden">
|
||||
<div className={cn('relative z-0 min-h-0 flex-1 overflow-hidden', isMac && 'no-drag')}>
|
||||
<div className={cn('h-full min-h-0', visibleTab !== 'changes' && 'hidden')}>
|
||||
<ChangesTab
|
||||
files={files}
|
||||
|
||||
@@ -768,7 +768,10 @@ export function Chat() {
|
||||
className={cn(
|
||||
'relative flex min-h-0 -m-6 overflow-hidden transition-colors duration-500',
|
||||
'bg-background',
|
||||
isMac && 'rounded-tl-2xl shadow-[inset_1px_1px_0_hsl(var(--border)/0.55)]',
|
||||
// Stack above MainLayout's mac-main-drag-region (z-10) so the right-hand
|
||||
// artifact/preview pane stays clickable; window drag is handled by the
|
||||
// sidebar + chat-toolbar drag strips instead.
|
||||
isMac && 'z-20 rounded-tl-2xl shadow-[inset_1px_1px_0_hsl(var(--border)/0.55)]',
|
||||
isWindows && 'rounded-tl-2xl',
|
||||
)}
|
||||
style={{ height: isMac ? '100vh' : 'calc(100vh - 2.5rem)' }}
|
||||
@@ -1022,7 +1025,11 @@ export function Chat() {
|
||||
<PanelResizeDividerLazy containerRef={splitContainerRef} />
|
||||
</Suspense>
|
||||
<aside
|
||||
className="relative z-20 hidden shrink-0 border-l border-black/5 dark:border-white/10 lg:flex lg:flex-col"
|
||||
data-testid="artifact-panel-aside"
|
||||
className={cn(
|
||||
'relative z-20 hidden shrink-0 border-l border-black/5 dark:border-white/10 lg:flex lg:flex-col',
|
||||
isMac && 'no-drag',
|
||||
)}
|
||||
style={{ width: `${panelWidthPct}%` }}
|
||||
>
|
||||
<Suspense
|
||||
|
||||
@@ -3,7 +3,7 @@ import { expect, test } from './fixtures/electron';
|
||||
test.describe('macOS frameless chrome', () => {
|
||||
test.skip(process.platform !== 'darwin', 'macOS drag-region chrome only');
|
||||
|
||||
test('keeps a draggable strip above the right content pane', async ({ page }) => {
|
||||
test('keeps a draggable strip above non-chat pages and stacks chat above it', async ({ page }) => {
|
||||
await expect(page.getByTestId('setup-page')).toBeVisible();
|
||||
await page.getByTestId('setup-skip-button').click();
|
||||
|
||||
@@ -18,5 +18,9 @@ test.describe('macOS frameless chrome', () => {
|
||||
expect(box).not.toBeNull();
|
||||
expect(box!.width).toBeGreaterThan(200);
|
||||
expect(box!.height).toBeGreaterThanOrEqual(24);
|
||||
|
||||
const chatPage = page.getByTestId('chat-page');
|
||||
await expect(chatPage).toBeVisible();
|
||||
await expect(chatPage).toHaveCSS('z-index', '20');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -140,4 +140,32 @@ describe('ArtifactPanel', () => {
|
||||
fireEvent.pointerDown(changesButton, { button: 0 });
|
||||
expect(screen.getAllByTestId('file-preview-body')[0]).toHaveTextContent('diff');
|
||||
});
|
||||
|
||||
it('marks macOS chrome so preview tabs and content stay clickable', () => {
|
||||
window.electron.platform = 'darwin';
|
||||
|
||||
useArtifactPanel.setState({
|
||||
open: true,
|
||||
tab: 'preview',
|
||||
focusedFile: {
|
||||
filePath: '/tmp/demo.md',
|
||||
fileName: 'demo.md',
|
||||
ext: '.md',
|
||||
mimeType: 'text/markdown',
|
||||
contentType: 'document',
|
||||
},
|
||||
widthPct: ARTIFACT_PANEL_DEFAULT_WIDTH,
|
||||
});
|
||||
|
||||
render(
|
||||
<ArtifactPanel
|
||||
files={[makeGeneratedFile()]}
|
||||
agent={null}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByTestId('artifact-panel')).toHaveClass('no-drag');
|
||||
expect(screen.getByTestId('artifact-panel-drag-region')).toHaveClass('drag-region');
|
||||
expect(screen.getByTestId('artifact-panel-tab-preview').parentElement).toHaveClass('no-drag');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -102,6 +102,15 @@ describe('Chat artifact panel layout', () => {
|
||||
expect(actions).toHaveClass('no-drag');
|
||||
});
|
||||
|
||||
it('stacks the chat page above the macOS main drag strip', async () => {
|
||||
window.electron.platform = 'darwin';
|
||||
|
||||
render(<Chat />);
|
||||
|
||||
const chatPage = await screen.findByTestId('chat-page');
|
||||
expect(chatPage).toHaveClass('z-20');
|
||||
});
|
||||
|
||||
it('layers the right artifact panel above the macOS drag strip', async () => {
|
||||
window.electron.platform = 'darwin';
|
||||
|
||||
@@ -112,5 +121,6 @@ describe('Chat artifact panel layout', () => {
|
||||
|
||||
expect(aside).toHaveClass('relative');
|
||||
expect(aside).toHaveClass('z-20');
|
||||
expect(aside).toHaveClass('no-drag');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user