diff --git a/apps/desktop/src/components/ui/pane-tab.test.tsx b/apps/desktop/src/components/ui/pane-tab.test.tsx index c36f03fb8ada..60943b85b848 100644 --- a/apps/desktop/src/components/ui/pane-tab.test.tsx +++ b/apps/desktop/src/components/ui/pane-tab.test.tsx @@ -90,3 +90,54 @@ describe('PaneTab close gestures', () => { expect(onPointerDown).toHaveBeenCalledTimes(1) }) }) + +describe('PaneTab hover close button', () => { + it('renders a close button when onClose is set on a horizontal tab', () => { + const onClose = vi.fn() + render( + + tab + + ) + + expect(screen.getByRole('button', { name: 'Close tab' })).toBeTruthy() + }) + + it('clicking the close button calls onClose and stops propagation', () => { + const onClose = vi.fn() + const onTabPointerDown = vi.fn() + render( + + tab + + ) + + const closeBtn = screen.getByRole('button', { name: 'Close tab' }) + fireEvent.pointerDown(closeBtn) + fireEvent.click(closeBtn) + expect(onClose).toHaveBeenCalledTimes(1) + // The tab's own pointerdown handler must NOT fire — the X is a leaf action. + expect(onTabPointerDown).not.toHaveBeenCalled() + }) + + it('does not render a close button on vertical tabs', () => { + const onClose = vi.fn() + render( + + tab + + ) + + expect(screen.queryByRole('button', { name: 'Close tab' })).toBeNull() + }) + + it('does not render a close button without onClose', () => { + render( + + tab + + ) + + expect(screen.queryByRole('button', { name: 'Close tab' })).toBeNull() + }) +}) diff --git a/apps/desktop/src/components/ui/pane-tab.tsx b/apps/desktop/src/components/ui/pane-tab.tsx index da8baae2aec9..3e3b4851ec20 100644 --- a/apps/desktop/src/components/ui/pane-tab.tsx +++ b/apps/desktop/src/components/ui/pane-tab.tsx @@ -1,5 +1,6 @@ import * as React from 'react' +import { Codicon } from '@/components/ui/codicon' import { cn } from '@/lib/utils' /** Inset bottom stroke for a horizontal tab strip — titlebar color, cut by the active tab. */ @@ -27,8 +28,8 @@ const TAB_IDLE = interface PaneTabProps extends React.ComponentProps<'div'> { active?: boolean dirty?: boolean - /** Close gesture, no hover X (too easy to hit on small tabs): middle-click, - * or ⌘-click as the trackpad-friendly Mac equivalent. */ + /** Close gesture: hover X on horizontal tabs, plus middle-click and ⌘-click + * (the trackpad-friendly Mac equivalent). */ onClose?: () => void /** Vertical rail form (collapsed sidebar zones). */ vertical?: boolean @@ -127,7 +128,35 @@ export const PaneTab = React.forwardRef(function P {...props} > {children} - {dirty && ( + {onClose && !vertical && ( + + )} + {dirty && !(onClose && !vertical) && (