feat(app): improve projects sidebar reactivity (#34391)

Co-authored-by: Brendan Allan <git@brendonovich.dev>
This commit is contained in:
Aarav Sareen 2026-06-29 15:50:26 +05:30 committed by GitHub
commit 2070fd9bc2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 108 additions and 38 deletions

View file

@ -171,4 +171,66 @@
transform: rotate(360deg); transform: rotate(360deg);
} }
} }
@keyframes home-projects-fade-top {
from {
visibility: hidden;
}
to {
visibility: visible;
}
}
@keyframes home-projects-fade-bottom {
from {
visibility: visible;
}
to {
visibility: hidden;
}
}
[data-slot="home-projects-scroll"] {
timeline-scope: --home-projects-scroll;
}
[data-slot="home-projects-scroll"]::before,
[data-slot="home-projects-scroll"]::after {
content: "";
position: absolute;
left: 0;
right: 0;
z-index: 10;
height: 16px;
pointer-events: none;
visibility: hidden;
}
[data-slot="home-projects-scroll"]::before {
top: 0;
background: linear-gradient(to bottom, var(--v2-background-bg-base), transparent);
}
[data-slot="home-projects-scroll"]::after {
bottom: 0;
background: linear-gradient(to top, var(--v2-background-bg-base), transparent);
}
@supports (animation-timeline: --home-projects-scroll) and (timeline-scope: --home-projects-scroll) {
[data-slot="home-projects-scroll"] .scroll-view__viewport {
scroll-timeline: --home-projects-scroll y;
}
[data-slot="home-projects-scroll"]::before {
animation: home-projects-fade-top linear both;
animation-timeline: --home-projects-scroll;
animation-range: 0 0.1px;
}
[data-slot="home-projects-scroll"]::after {
animation: home-projects-fade-bottom linear both;
animation-timeline: --home-projects-scroll;
animation-range: calc(100% - 1.1px) calc(100% - 1px);
}
}
} }

View file

@ -668,10 +668,10 @@ function HomeProjectColumn(props: {
return ( return (
<aside <aside
class="mt-6 flex min-w-0 flex-col gap-4 lg:mt-14 lg:pt-[52px]" class="mt-6 flex min-h-0 min-w-0 flex-col gap-4 overflow-hidden lg:mt-14 lg:pt-[52px]"
aria-label={props.language.t("home.projects")} aria-label={props.language.t("home.projects")}
> >
<div class="flex h-7 min-w-0 items-center justify-between pl-1.5"> <div class="flex h-7 min-w-0 shrink-0 items-center justify-between pl-1.5">
<div class={HOME_SECTION_LABEL}>{props.language.t("home.projects")}</div> <div class={HOME_SECTION_LABEL}>{props.language.t("home.projects")}</div>
<Show when={global.servers.list().length === 1}> <Show when={global.servers.list().length === 1}>
<TooltipV2 placement="bottom" value={props.language.t("home.project.add")}> <TooltipV2 placement="bottom" value={props.language.t("home.project.add")}>
@ -688,43 +688,51 @@ function HomeProjectColumn(props: {
</TooltipV2> </TooltipV2>
</Show> </Show>
</div> </div>
<Show <ScrollView data-slot="home-projects-scroll" class="min-h-0 min-w-0 shrink">
when={global.servers.list().length > 1} <Show
fallback={<HomeProjectList {...props} server={global.servers.list()[0]!} />} when={global.servers.list().length > 1}
> fallback={
<For each={global.servers.list()}> <div class="pr-3">
{(item) => { <HomeProjectList {...props} server={global.servers.list()[0]!} />
const key = ServerConnection.key(item) </div>
const healthy = () => !!global.servers.health[key]?.healthy }
const serverCtx = global.ensureServerCtx(item) >
const projects = () => serverCtx.projects.list() <div class="flex min-w-0 flex-col gap-1 pr-3">
const hasProjects = () => projects().length > 0 <For each={global.servers.list()}>
const collapsed = () => !!state().collapsed[key] {(item) => {
return ( const key = ServerConnection.key(item)
<div class="flex max-h-[min(572px,calc(100vh_-_300px))] min-w-0 flex-col gap-1 overflow-y-auto [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"> const healthy = () => !!global.servers.health[key]?.healthy
<HomeServerRow const serverCtx = global.ensureServerCtx(item)
server={item} const projects = () => serverCtx.projects.list()
selected={props.selected.server === key && !props.selected.directory} const hasProjects = () => projects().length > 0
collapsed={collapsed()} const collapsed = () => !!state().collapsed[key]
health={global.servers.health[key]} return (
controller={controller} <div class="flex min-w-0 flex-col gap-1">
focusServer={props.focusServer} <HomeServerRow
chooseProject={props.chooseProject} server={item}
openEdit={(server) => dialog.show(() => <DialogServerV2 mode="edit" server={server} />)} selected={props.selected.server === key && !props.selected.directory}
toggleCollapsed={() => setState("collapsed", key, !state().collapsed[key])} collapsed={collapsed()}
language={props.language} health={global.servers.health[key]}
/> controller={controller}
<Show when={healthy() && hasProjects() && !collapsed()}> focusServer={props.focusServer}
<div class="mx-3 h-px bg-v2-border-border-base" /> chooseProject={props.chooseProject}
<HomeProjectList {...props} server={item} projects={projects()} /> openEdit={(server) => dialog.show(() => <DialogServerV2 mode="edit" server={server} />)}
</Show> toggleCollapsed={() => setState("collapsed", key, !state().collapsed[key])}
</div> language={props.language}
) />
}} <Show when={healthy() && hasProjects() && !collapsed()}>
</For> <div class="mx-3 h-px bg-v2-border-border-base" />
</Show> <HomeProjectList {...props} server={item} projects={projects()} />
</Show>
</div>
)
}}
</For>
</div>
</Show>
</ScrollView>
<HomeUtilityNav <HomeUtilityNav
class="mt-4 hidden lg:flex" class="mb-8 mt-4 hidden shrink-0 lg:flex"
openSettings={props.openSettings} openSettings={props.openSettings}
openHelp={props.openHelp} openHelp={props.openHelp}
language={props.language} language={props.language}