mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-07-17 13:13:35 +08:00
Restore tab close affordance after drag migration
The pointer-drag rewrite left the tab close control without its hover trigger, so tabs could still be closed logically but no longer exposed the affordance. This restores the hover group, trims the close icon to a lighter visual weight, and adds a regression test to keep close-click behavior from colliding with drag. Constraint: Tab close must remain compatible with the custom pointer-drag reorder flow Rejected: Reintroduce a larger hoverable close button | made the tab chrome look visually heavy Confidence: high Scope-risk: narrow Reversibility: clean Directive: Keep the close control visually subordinate to the tab label and verify drag-click interactions before changing tab hit areas Tested: cd desktop && bun run test -- TabBar; cd desktop && bun run lint Not-tested: Full desktop app manual visual QA in Tauri runtime
This commit is contained in:
parent
47b3cc4d40
commit
c05eeff644
@ -295,4 +295,42 @@ describe('TabBar', () => {
|
||||
expect(useTabStore.getState().tabs.map((tab) => tab.sessionId)).toEqual(['tab-1', 'tab-2'])
|
||||
expect(useTabStore.getState().activeTabId).toBe('tab-1')
|
||||
})
|
||||
|
||||
it('closes a tab from the close button without activating drag behavior', async () => {
|
||||
const { TabBar } = await import('./TabBar')
|
||||
const { useTabStore } = await import('../../stores/tabStore')
|
||||
const { useChatStore } = await import('../../stores/chatStore')
|
||||
|
||||
const disconnectSession = vi.fn()
|
||||
|
||||
useTabStore.setState({
|
||||
tabs: [
|
||||
{ sessionId: 'tab-1', title: 'First Session', type: 'session', status: 'idle' },
|
||||
{ sessionId: 'tab-2', title: 'Second Session', type: 'session', status: 'idle' },
|
||||
],
|
||||
activeTabId: 'tab-2',
|
||||
})
|
||||
useChatStore.setState({
|
||||
sessions: {},
|
||||
disconnectSession,
|
||||
} as Partial<ReturnType<typeof useChatStore.getState>>)
|
||||
|
||||
await act(async () => {
|
||||
render(<TabBar />)
|
||||
})
|
||||
|
||||
const firstTab = screen.getByText('First Session').closest('.tab-bar-hit-area')
|
||||
const closeButton = screen.getByLabelText('Close First Session')
|
||||
|
||||
expect(firstTab).toHaveClass('group')
|
||||
|
||||
fireEvent.mouseDown(closeButton, { button: 0, clientX: 20, clientY: 10 })
|
||||
fireEvent.click(closeButton)
|
||||
fireEvent.mouseMove(window, { clientX: 260, clientY: 10 })
|
||||
fireEvent.mouseUp(window)
|
||||
|
||||
expect(disconnectSession).toHaveBeenCalledWith('tab-1')
|
||||
expect(useTabStore.getState().tabs.map((tab) => tab.sessionId)).toEqual(['tab-2'])
|
||||
expect(useTabStore.getState().activeTabId).toBe('tab-2')
|
||||
})
|
||||
})
|
||||
|
||||
@ -374,7 +374,7 @@ const TabItem = forwardRef<HTMLDivElement, {
|
||||
onMouseDown={onMouseDown}
|
||||
onContextMenu={onContextMenu}
|
||||
className={`
|
||||
tab-bar-hit-area flex-shrink-0 flex items-center gap-1.5 px-3 min-h-[37px] relative
|
||||
tab-bar-hit-area group flex-shrink-0 flex items-center gap-1.5 px-3 min-h-[37px] relative
|
||||
${isDragging ? 'z-20 cursor-grabbing' : 'cursor-grab'}
|
||||
transition-[background-color,box-shadow,opacity,transform] duration-150 ease-out
|
||||
${isActive
|
||||
@ -408,11 +408,13 @@ const TabItem = forwardRef<HTMLDivElement, {
|
||||
</span>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
aria-label={`Close ${tab.title || 'Untitled'}`}
|
||||
onMouseDown={(e) => { e.stopPropagation() }}
|
||||
onClick={(e) => { e.stopPropagation(); onClose() }}
|
||||
className="flex-shrink-0 w-4 h-4 flex items-center justify-center rounded opacity-0 group-hover:opacity-100 hover:bg-[var(--color-surface-hover)] transition-opacity text-[var(--color-text-tertiary)] hover:text-[var(--color-text-primary)]"
|
||||
className="flex-shrink-0 -mr-0.5 inline-flex h-3 w-3 items-center justify-center bg-transparent p-0 opacity-0 group-hover:opacity-100 focus-visible:opacity-100 transition-[opacity,color] text-[var(--color-text-tertiary)] hover:text-[var(--color-text-secondary)] focus-visible:outline-none"
|
||||
>
|
||||
<span className="material-symbols-outlined text-[14px]">close</span>
|
||||
<span className="material-symbols-outlined text-[11px] leading-none">close</span>
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user