Skip to content

Commit 4863879

Browse files
Merge pull request #4 from PraneethKulukuri26/feature/tab-context-menu
feat: add right-click context menu to tabs with close options
2 parents fbd55bd + 65109bc commit 4863879

2 files changed

Lines changed: 83 additions & 1 deletion

File tree

src/renderer/src/components/TabsBar.tsx

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useRef, useEffect } from 'react'
1+
import React, { useRef, useEffect, useState } from 'react'
22
import { useAppStore } from '@/stores/appStore'
33
import { useAllProjectApis } from '@/hooks/useApis'
44
import { METHOD_COLORS } from '@/types'
@@ -7,6 +7,18 @@ export function TabsBar() {
77
const { openTabs, activeTabId, setActiveTab, closeTab, currentProjectId, apiDrafts } = useAppStore()
88
const { data: allApis } = useAllProjectApis(currentProjectId)
99
const containerRef = useRef<HTMLDivElement>(null)
10+
const [ctx, setCtx] = useState<{ x: number; y: number; tabId: string } | null>(null)
11+
12+
useEffect(() => {
13+
const handleEscape = (e: KeyboardEvent) => { if (e.key === 'Escape') setCtx(null) }
14+
const handleClick = () => setCtx(null)
15+
window.addEventListener('keydown', handleEscape)
16+
window.addEventListener('click', handleClick)
17+
return () => {
18+
window.removeEventListener('keydown', handleEscape)
19+
window.removeEventListener('click', handleClick)
20+
}
21+
}, [])
1022

1123
// Scroll active tab into view
1224
useEffect(() => {
@@ -46,6 +58,10 @@ export function TabsBar() {
4658
onMouseDown={(e) => {
4759
if (e.button === 1) closeTab(tab.id) // Middle click to close
4860
}}
61+
onContextMenu={(e) => {
62+
e.preventDefault()
63+
setCtx({ x: e.clientX, y: e.clientY, tabId: tab.id })
64+
}}
4965
style={{
5066
display: 'flex',
5167
alignItems: 'center',
@@ -144,6 +160,55 @@ export function TabsBar() {
144160
</div>
145161
)
146162
})}
163+
164+
{ctx && (
165+
<div
166+
style={{
167+
position: 'fixed',
168+
top: ctx.y,
169+
left: ctx.x,
170+
background: '#1F1F1F',
171+
border: '1px solid #2A2A2A',
172+
borderRadius: '6px',
173+
padding: '4px',
174+
display: 'flex',
175+
flexDirection: 'column',
176+
gap: '2px',
177+
zIndex: 1000,
178+
boxShadow: '0 4px 12px rgba(0,0,0,0.5)',
179+
minWidth: '160px'
180+
}}
181+
onClick={e => e.stopPropagation()}
182+
>
183+
<button
184+
className="ctx-menu-item"
185+
onClick={() => { closeTab(ctx.tabId); setCtx(null) }}
186+
style={{ display: 'flex', alignItems: 'center', padding: '6px 12px', fontSize: '12px', color: '#E5E5E5', background: 'transparent', border: 'none', borderRadius: '4px', cursor: 'pointer', textAlign: 'left', transition: '150ms ease' }}
187+
onMouseEnter={e => e.currentTarget.style.background = '#2A2A2A'}
188+
onMouseLeave={e => e.currentTarget.style.background = 'transparent'}
189+
>
190+
Close Tab
191+
</button>
192+
<button
193+
className="ctx-menu-item"
194+
onClick={() => { useAppStore.getState().closeOtherTabs(ctx.tabId); setCtx(null) }}
195+
style={{ display: 'flex', alignItems: 'center', padding: '6px 12px', fontSize: '12px', color: '#E5E5E5', background: 'transparent', border: 'none', borderRadius: '4px', cursor: 'pointer', textAlign: 'left', transition: '150ms ease' }}
196+
onMouseEnter={e => e.currentTarget.style.background = '#2A2A2A'}
197+
onMouseLeave={e => e.currentTarget.style.background = 'transparent'}
198+
>
199+
Close Other Tabs
200+
</button>
201+
<button
202+
className="ctx-menu-item"
203+
onClick={() => { useAppStore.getState().closeAllTabs(); setCtx(null) }}
204+
style={{ display: 'flex', alignItems: 'center', padding: '6px 12px', fontSize: '12px', color: '#EF4444', background: 'transparent', border: 'none', borderRadius: '4px', cursor: 'pointer', textAlign: 'left', transition: '150ms ease' }}
205+
onMouseEnter={e => e.currentTarget.style.background = '#2A2A2A'}
206+
onMouseLeave={e => e.currentTarget.style.background = 'transparent'}
207+
>
208+
Close All Tabs
209+
</button>
210+
</div>
211+
)}
147212
</div>
148213
)
149214
}

src/renderer/src/stores/appStore.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ interface AppState {
6565
selectApi: (apiId: string | null, folderId?: string) => void
6666
openDocsTab: () => void
6767
closeTab: (tabId: string) => void
68+
closeOtherTabs: (tabId: string) => void
69+
closeAllTabs: () => void
6870
setActiveTab: (tabId: string) => void
6971
validateTabs: (validApiIds: string[]) => void
7072
selectEnvironment: (id: string | null) => void
@@ -214,6 +216,21 @@ export const useAppStore = create<AppState>((set) => ({
214216
}
215217
return { openTabs: newTabs }
216218
}),
219+
closeOtherTabs: (tabId) => set((s) => {
220+
const tabToKeep = s.openTabs.find(t => t.id === tabId)
221+
if (!tabToKeep) return s
222+
let newState: Partial<AppState> = { openTabs: [tabToKeep] }
223+
newState.activeTabId = tabId
224+
if (tabToKeep.type === 'docs') {
225+
newState.showApiDocumentation = true
226+
newState.currentApiId = null
227+
} else {
228+
newState.showApiDocumentation = false
229+
newState.currentApiId = tabToKeep.apiId || null
230+
}
231+
return newState as AppState
232+
}),
233+
closeAllTabs: () => set({ openTabs: [], activeTabId: null, currentApiId: null, showApiDocumentation: false }),
217234
setActiveTab: (tabId) => set((s) => {
218235
const tab = s.openTabs.find(t => t.id === tabId)
219236
if (!tab) return s

0 commit comments

Comments
 (0)