refactor(desktop): enhance project tile interaction with suppress hover functionality (#15214)

This commit is contained in:
OpeOginni
2026-02-26 18:00:11 +01:00
committed by GitHub
parent 08f056d412
commit 5745ee87ba

View File

@@ -1,4 +1,5 @@
import { createEffect, createMemo, createSignal, For, Show, type Accessor, type JSX } from "solid-js"
import { createEffect, createMemo, For, Show, type Accessor, type JSX } from "solid-js"
import { createStore } from "solid-js/store"
import { base64Encode } from "@opencode-ai/util/encode"
import { Button } from "@opencode-ai/ui/button"
import { ContextMenu } from "@opencode-ai/ui/context-menu"
@@ -7,7 +8,7 @@ import { Icon } from "@opencode-ai/ui/icon"
import { IconButton } from "@opencode-ai/ui/icon-button"
import { Tooltip } from "@opencode-ai/ui/tooltip"
import { createSortable } from "@thisbeyond/solid-dnd"
import { type LocalProject } from "@/context/layout"
import { useLayout, type LocalProject } from "@/context/layout"
import { useGlobalSync } from "@/context/global-sync"
import { useLanguage } from "@/context/language"
import { useNotification } from "@/context/notification"
@@ -60,6 +61,7 @@ const ProjectTile = (props: {
selected: Accessor<boolean>
active: Accessor<boolean>
overlay: Accessor<boolean>
suppressHover: Accessor<boolean>
dirs: Accessor<string[]>
onProjectMouseEnter: (worktree: string, event: MouseEvent) => void
onProjectMouseLeave: (worktree: string) => void
@@ -71,9 +73,11 @@ const ProjectTile = (props: {
closeProject: (directory: string) => void
setMenu: (value: boolean) => void
setOpen: (value: boolean) => void
setSuppressHover: (value: boolean) => void
language: ReturnType<typeof useLanguage>
}): JSX.Element => {
const notification = useNotification()
const layout = useLayout()
const unseenCount = createMemo(() =>
props.dirs().reduce((total, directory) => total + notification.project.unseenCount(directory), 0),
)
@@ -107,17 +111,28 @@ const ProjectTile = (props: {
}}
onMouseEnter={(event: MouseEvent) => {
if (!props.overlay()) return
if (props.suppressHover()) return
props.onProjectMouseEnter(props.project.worktree, event)
}}
onMouseLeave={() => {
if (props.suppressHover()) props.setSuppressHover(false)
if (!props.overlay()) return
props.onProjectMouseLeave(props.project.worktree)
}}
onFocus={() => {
if (!props.overlay()) return
if (props.suppressHover()) return
props.onProjectFocus(props.project.worktree)
}}
onClick={() => props.navigateToProject(props.project.worktree)}
onClick={() => {
if (props.selected()) {
props.setSuppressHover(true)
layout.sidebar.toggle()
return
}
props.setSuppressHover(false)
props.navigateToProject(props.project.worktree)
}}
onBlur={() => props.setOpen(false)}
>
<ProjectIcon project={props.project} notify />
@@ -278,16 +293,19 @@ export const SortableProject = (props: {
const workspaces = createMemo(() => props.ctx.workspaceIds(props.project).slice(0, 2))
const workspaceEnabled = createMemo(() => props.ctx.workspacesEnabled(props.project))
const dirs = createMemo(() => props.ctx.workspaceIds(props.project))
const [open, setOpen] = createSignal(false)
const [menu, setMenu] = createSignal(false)
const [state, setState] = createStore({
open: false,
menu: false,
suppressHover: false,
})
const preview = createMemo(() => !props.mobile && props.ctx.sidebarOpened())
const overlay = createMemo(() => !props.mobile && !props.ctx.sidebarOpened())
const active = createMemo(() =>
projectTileActive({
menu: menu(),
menu: state.menu,
preview: preview(),
open: open(),
open: state.open,
overlay: overlay(),
hoverProject: props.ctx.hoverProject(),
worktree: props.project.worktree,
@@ -296,8 +314,14 @@ export const SortableProject = (props: {
createEffect(() => {
if (preview()) return
if (!open()) return
setOpen(false)
if (!state.open) return
setState("open", false)
})
createEffect(() => {
if (!selected()) return
if (!state.open) return
setState("open", false)
})
const label = (directory: string) => {
@@ -328,6 +352,7 @@ export const SortableProject = (props: {
selected={selected}
active={active}
overlay={overlay}
suppressHover={() => state.suppressHover}
dirs={dirs}
onProjectMouseEnter={props.ctx.onProjectMouseEnter}
onProjectMouseLeave={props.ctx.onProjectMouseLeave}
@@ -337,8 +362,9 @@ export const SortableProject = (props: {
toggleProjectWorkspaces={props.ctx.toggleProjectWorkspaces}
workspacesEnabled={props.ctx.workspacesEnabled}
closeProject={props.ctx.closeProject}
setMenu={setMenu}
setOpen={setOpen}
setMenu={(value) => setState("menu", value)}
setOpen={(value) => setState("open", value)}
setSuppressHover={(value) => setState("suppressHover", value)}
language={language}
/>
)
@@ -346,17 +372,18 @@ export const SortableProject = (props: {
return (
// @ts-ignore
<div use:sortable classList={{ "opacity-30": sortable.isActiveDraggable }}>
<Show when={preview()} fallback={tile()}>
<Show when={preview() && !selected()} fallback={tile()}>
<HoverCard
open={open() && !menu()}
open={!state.suppressHover && state.open && !state.menu}
openDelay={0}
closeDelay={0}
placement="right-start"
gutter={6}
trigger={tile()}
onOpenChange={(value) => {
if (menu()) return
setOpen(value)
if (state.menu) return
if (value && state.suppressHover) return
setState("open", value)
if (value) props.ctx.setHoverSession(undefined)
}}
>
@@ -371,7 +398,7 @@ export const SortableProject = (props: {
projectChildren={projectChildren}
workspaceSessions={workspaceSessions}
workspaceChildren={workspaceChildren}
setOpen={setOpen}
setOpen={(value) => setState("open", value)}
ctx={props.ctx}
language={language}
/>