Skip to content

Commit d796ca4

Browse files
committed
feat(sidebar): context menu for nav items in sidebar
1 parent 796f73e commit d796ca4

File tree

4 files changed

+127
-0
lines changed

4 files changed

+127
-0
lines changed

apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export { HelpModal } from './help-modal/help-modal'
2+
export { NavItemContextMenu } from './nav-item-context-menu'
23
export { SearchModal } from './search-modal/search-modal'
34
export { SettingsModal } from './settings-modal/settings-modal'
45
export { UsageIndicator } from './usage-indicator/usage-indicator'
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { NavItemContextMenu } from './nav-item-context-menu'
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
'use client'
2+
3+
import { Popover, PopoverAnchor, PopoverContent, PopoverItem } from '@/components/emcn'
4+
5+
interface NavItemContextMenuProps {
6+
/**
7+
* Whether the context menu is open
8+
*/
9+
isOpen: boolean
10+
/**
11+
* Position of the context menu
12+
*/
13+
position: { x: number; y: number }
14+
/**
15+
* Ref for the menu element
16+
*/
17+
menuRef: React.RefObject<HTMLDivElement | null>
18+
/**
19+
* Callback when menu should close
20+
*/
21+
onClose: () => void
22+
/**
23+
* Callback when open in new tab is clicked
24+
*/
25+
onOpenInNewTab: () => void
26+
/**
27+
* Callback when copy link is clicked
28+
*/
29+
onCopyLink: () => void
30+
}
31+
32+
/**
33+
* Context menu component for sidebar navigation items.
34+
* Displays navigation-appropriate options (open in new tab, copy link) in a popover at the right-click position.
35+
*/
36+
export function NavItemContextMenu({
37+
isOpen,
38+
position,
39+
menuRef,
40+
onClose,
41+
onOpenInNewTab,
42+
onCopyLink,
43+
}: NavItemContextMenuProps) {
44+
return (
45+
<Popover
46+
open={isOpen}
47+
onOpenChange={onClose}
48+
variant='secondary'
49+
size='sm'
50+
colorScheme='inverted'
51+
>
52+
<PopoverAnchor
53+
style={{
54+
position: 'fixed',
55+
left: `${position.x}px`,
56+
top: `${position.y}px`,
57+
width: '1px',
58+
height: '1px',
59+
}}
60+
/>
61+
<PopoverContent ref={menuRef} align='start' side='bottom' sideOffset={4}>
62+
<PopoverItem
63+
onClick={() => {
64+
onOpenInNewTab()
65+
onClose()
66+
}}
67+
>
68+
Open in new tab
69+
</PopoverItem>
70+
<PopoverItem
71+
onClick={() => {
72+
onCopyLink()
73+
onClose()
74+
}}
75+
>
76+
Copy link
77+
</PopoverItem>
78+
</PopoverContent>
79+
</Popover>
80+
)
81+
}

apps/sim/app/workspace/[workspaceId]/w/components/sidebar/sidebar.tsx

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,15 @@ import { useUserPermissionsContext } from '@/app/workspace/[workspaceId]/provide
1313
import { createCommands } from '@/app/workspace/[workspaceId]/utils/commands-utils'
1414
import {
1515
HelpModal,
16+
NavItemContextMenu,
1617
SearchModal,
1718
SettingsModal,
1819
UsageIndicator,
1920
WorkflowList,
2021
WorkspaceHeader,
2122
} from '@/app/workspace/[workspaceId]/w/components/sidebar/components'
2223
import {
24+
useContextMenu,
2325
useFolderOperations,
2426
useSidebarResize,
2527
useWorkflowOperations,
@@ -168,6 +170,37 @@ export function Sidebar() {
168170
workspaceId,
169171
})
170172

173+
/** Context menu state for navigation items */
174+
const [activeNavItemHref, setActiveNavItemHref] = useState<string | null>(null)
175+
const {
176+
isOpen: isNavContextMenuOpen,
177+
position: navContextMenuPosition,
178+
menuRef: navMenuRef,
179+
handleContextMenu: handleNavContextMenuBase,
180+
closeMenu: closeNavContextMenu,
181+
} = useContextMenu()
182+
183+
const handleNavItemContextMenu = useCallback(
184+
(e: React.MouseEvent, href: string) => {
185+
setActiveNavItemHref(href)
186+
handleNavContextMenuBase(e)
187+
},
188+
[handleNavContextMenuBase]
189+
)
190+
191+
const handleNavOpenInNewTab = useCallback(() => {
192+
if (activeNavItemHref) {
193+
window.open(activeNavItemHref, '_blank')
194+
}
195+
}, [activeNavItemHref])
196+
197+
const handleNavCopyLink = useCallback(async () => {
198+
if (activeNavItemHref) {
199+
const fullUrl = `${window.location.origin}${activeNavItemHref}`
200+
await navigator.clipboard.writeText(fullUrl)
201+
}
202+
}, [activeNavItemHref])
203+
171204
const { handleDuplicateWorkspace: duplicateWorkspace } = useDuplicateWorkspace({
172205
getWorkspaceId: () => workspaceId,
173206
})
@@ -629,12 +662,23 @@ export function Sidebar() {
629662
href={item.href!}
630663
data-item-id={item.id}
631664
className={`${baseClasses} ${activeClasses}`}
665+
onContextMenu={(e) => handleNavItemContextMenu(e, item.href!)}
632666
>
633667
{content}
634668
</Link>
635669
)
636670
})}
637671
</div>
672+
673+
{/* Nav Item Context Menu */}
674+
<NavItemContextMenu
675+
isOpen={isNavContextMenuOpen}
676+
position={navContextMenuPosition}
677+
menuRef={navMenuRef}
678+
onClose={closeNavContextMenu}
679+
onOpenInNewTab={handleNavOpenInNewTab}
680+
onCopyLink={handleNavCopyLink}
681+
/>
638682
</div>
639683
</aside>
640684

0 commit comments

Comments
 (0)