@@ -31,6 +31,7 @@ import { Tooltip, TooltipKeybind } from "@opencode-ai/ui/tooltip"
3131import { HoverCard } from "@opencode-ai/ui/hover-card"
3232import { MessageNav } from "@opencode-ai/ui/message-nav"
3333import { DropdownMenu } from "@opencode-ai/ui/dropdown-menu"
34+ import { ContextMenu } from "@opencode-ai/ui/context-menu"
3435import { Collapsible } from "@opencode-ai/ui/collapsible"
3536import { DiffChanges } from "@opencode-ai/ui/diff-changes"
3637import { Spinner } from "@opencode-ai/ui/spinner"
@@ -2310,10 +2311,13 @@ export default function Layout(props: ParentProps) {
23102311 ( ) => props . project . vcs === "git" && layout . sidebar . workspaces ( props . project . worktree ) ( ) ,
23112312 )
23122313 const [ open , setOpen ] = createSignal ( false )
2314+ const [ menu , setMenu ] = createSignal ( false )
23132315
23142316 const preview = createMemo ( ( ) => ! props . mobile && layout . sidebar . opened ( ) )
23152317 const overlay = createMemo ( ( ) => ! props . mobile && ! layout . sidebar . opened ( ) )
2316- const active = createMemo ( ( ) => ( preview ( ) ? open ( ) : overlay ( ) && state . hoverProject === props . project . worktree ) )
2318+ const active = createMemo (
2319+ ( ) => menu ( ) || ( preview ( ) ? open ( ) : overlay ( ) && state . hoverProject === props . project . worktree ) ,
2320+ )
23172321
23182322 createEffect ( ( ) => {
23192323 if ( preview ( ) ) return
@@ -2352,49 +2356,94 @@ export default function Layout(props: ParentProps) {
23522356
23532357 const projectName = ( ) => props . project . name || getFilename ( props . project . worktree )
23542358 const trigger = (
2355- < button
2356- type = "button"
2357- aria-label = { projectName ( ) }
2358- data-action = "project-switch"
2359- data-project = { base64Encode ( props . project . worktree ) }
2360- classList = { {
2361- "flex items-center justify-center size-10 p-1 rounded-lg overflow-hidden transition-colors cursor-default" : true ,
2362- "bg-transparent border-2 border-icon-strong-base hover:bg-surface-base-hover" : selected ( ) ,
2363- "bg-transparent border border-transparent hover:bg-surface-base-hover hover:border-border-weak-base" :
2364- ! selected ( ) && ! active ( ) ,
2365- "bg-surface-base-hover border border-border-weak-base" : ! selected ( ) && active ( ) ,
2366- } }
2367- onMouseEnter = { ( ) => {
2368- if ( ! overlay ( ) ) return
2369- globalSync . child ( props . project . worktree )
2370- setState ( "hoverProject" , props . project . worktree )
2371- setState ( "hoverSession" , undefined )
2359+ < ContextMenu
2360+ modal = { ! sidebarHovering ( ) }
2361+ onOpenChange = { ( value ) => {
2362+ setMenu ( value )
2363+ if ( value ) setOpen ( false )
23722364 } }
2373- onFocus = { ( ) => {
2374- if ( ! overlay ( ) ) return
2375- globalSync . child ( props . project . worktree )
2376- setState ( "hoverProject" , props . project . worktree )
2377- setState ( "hoverSession" , undefined )
2378- } }
2379- onClick = { ( ) => navigateToProject ( props . project . worktree ) }
2380- onBlur = { ( ) => setOpen ( false ) }
23812365 >
2382- < ProjectIcon project = { props . project } notify />
2383- </ button >
2366+ < ContextMenu . Trigger
2367+ as = "button"
2368+ type = "button"
2369+ aria-label = { projectName ( ) }
2370+ data-action = "project-switch"
2371+ data-project = { base64Encode ( props . project . worktree ) }
2372+ classList = { {
2373+ "flex items-center justify-center size-10 p-1 rounded-lg overflow-hidden transition-colors cursor-default" : true ,
2374+ "bg-transparent border-2 border-icon-strong-base hover:bg-surface-base-hover" : selected ( ) ,
2375+ "bg-transparent border border-transparent hover:bg-surface-base-hover hover:border-border-weak-base" :
2376+ ! selected ( ) && ! active ( ) ,
2377+ "bg-surface-base-hover border border-border-weak-base" : ! selected ( ) && active ( ) ,
2378+ } }
2379+ onMouseEnter = { ( ) => {
2380+ if ( ! overlay ( ) ) return
2381+ globalSync . child ( props . project . worktree )
2382+ setState ( "hoverProject" , props . project . worktree )
2383+ setState ( "hoverSession" , undefined )
2384+ } }
2385+ onFocus = { ( ) => {
2386+ if ( ! overlay ( ) ) return
2387+ globalSync . child ( props . project . worktree )
2388+ setState ( "hoverProject" , props . project . worktree )
2389+ setState ( "hoverSession" , undefined )
2390+ } }
2391+ onClick = { ( ) => navigateToProject ( props . project . worktree ) }
2392+ onBlur = { ( ) => setOpen ( false ) }
2393+ >
2394+ < ProjectIcon project = { props . project } notify />
2395+ </ ContextMenu . Trigger >
2396+ < ContextMenu . Portal mount = { ! props . mobile ? state . nav : undefined } >
2397+ < ContextMenu . Content >
2398+ < ContextMenu . Item onSelect = { ( ) => dialog . show ( ( ) => < DialogEditProject project = { props . project } /> ) } >
2399+ < ContextMenu . ItemLabel > { language . t ( "common.edit" ) } </ ContextMenu . ItemLabel >
2400+ </ ContextMenu . Item >
2401+ < ContextMenu . Item
2402+ data-action = "project-workspaces-toggle"
2403+ data-project = { base64Encode ( props . project . worktree ) }
2404+ disabled = { props . project . vcs !== "git" && ! layout . sidebar . workspaces ( props . project . worktree ) ( ) }
2405+ onSelect = { ( ) => {
2406+ const enabled = layout . sidebar . workspaces ( props . project . worktree ) ( )
2407+ if ( enabled ) {
2408+ layout . sidebar . toggleWorkspaces ( props . project . worktree )
2409+ return
2410+ }
2411+ if ( props . project . vcs !== "git" ) return
2412+ layout . sidebar . toggleWorkspaces ( props . project . worktree )
2413+ } }
2414+ >
2415+ < ContextMenu . ItemLabel >
2416+ { layout . sidebar . workspaces ( props . project . worktree ) ( )
2417+ ? language . t ( "sidebar.workspaces.disable" )
2418+ : language . t ( "sidebar.workspaces.enable" ) }
2419+ </ ContextMenu . ItemLabel >
2420+ </ ContextMenu . Item >
2421+ < ContextMenu . Separator />
2422+ < ContextMenu . Item
2423+ data-action = "project-close-menu"
2424+ data-project = { base64Encode ( props . project . worktree ) }
2425+ onSelect = { ( ) => closeProject ( props . project . worktree ) }
2426+ >
2427+ < ContextMenu . ItemLabel > { language . t ( "common.close" ) } </ ContextMenu . ItemLabel >
2428+ </ ContextMenu . Item >
2429+ </ ContextMenu . Content >
2430+ </ ContextMenu . Portal >
2431+ </ ContextMenu >
23842432 )
23852433
23862434 return (
23872435 // @ts -ignore
23882436 < div use :sortable classList = { { "opacity-30" : sortable . isActiveDraggable } } >
23892437 < Show when = { preview ( ) } fallback = { trigger } >
23902438 < HoverCard
2391- open = { open ( ) }
2439+ open = { open ( ) && ! menu ( ) }
23922440 openDelay = { 0 }
23932441 closeDelay = { 0 }
23942442 placement = "right-start"
23952443 gutter = { 6 }
23962444 trigger = { trigger }
23972445 onOpenChange = { ( value ) => {
2446+ if ( menu ( ) ) return
23982447 setOpen ( value )
23992448 if ( value ) setState ( "hoverSession" , undefined )
24002449 } }
0 commit comments