fix(ui): remove left sidebar and back/forward nav when no project

pull/17826/head
David Hill 2026-03-16 16:38:41 +00:00
parent e718db624f
commit eb0a67695e
2 changed files with 182 additions and 161 deletions

View File

@ -58,6 +58,7 @@ export function Titlebar() {
}) })
const path = () => `${location.pathname}${location.search}${location.hash}` const path = () => `${location.pathname}${location.search}${location.hash}`
const home = createMemo(() => !params.dir)
const creating = createMemo(() => { const creating = createMemo(() => {
if (!params.dir) return false if (!params.dir) return false
if (params.id) return false if (params.id) return false
@ -175,113 +176,119 @@ export function Titlebar() {
> >
<Show when={mac()}> <Show when={mac()}>
<div class="h-full shrink-0" style={{ width: `${72 / zoom()}px` }} /> <div class="h-full shrink-0" style={{ width: `${72 / zoom()}px` }} />
<div class="xl:hidden w-10 shrink-0 flex items-center justify-center"> <Show when={!home()}>
<IconButton <div class="xl:hidden w-10 shrink-0 flex items-center justify-center">
icon="menu" <IconButton
variant="ghost" icon="menu"
class="titlebar-icon rounded-md" variant="ghost"
onClick={layout.mobileSidebar.toggle} class="titlebar-icon rounded-md"
aria-label={language.t("sidebar.menu.toggle")} onClick={layout.mobileSidebar.toggle}
aria-expanded={layout.mobileSidebar.opened()} aria-label={language.t("sidebar.menu.toggle")}
/> aria-expanded={layout.mobileSidebar.opened()}
</div> />
</div>
</Show>
</Show> </Show>
<Show when={!mac()}> <Show when={!mac()}>
<div class="xl:hidden w-[48px] shrink-0 flex items-center justify-center"> <Show when={!home()}>
<IconButton <div class="xl:hidden w-[48px] shrink-0 flex items-center justify-center">
icon="menu" <IconButton
variant="ghost" icon="menu"
class="titlebar-icon rounded-md" variant="ghost"
onClick={layout.mobileSidebar.toggle} class="titlebar-icon rounded-md"
aria-label={language.t("sidebar.menu.toggle")} onClick={layout.mobileSidebar.toggle}
aria-expanded={layout.mobileSidebar.opened()} aria-label={language.t("sidebar.menu.toggle")}
/> aria-expanded={layout.mobileSidebar.opened()}
</div> />
</div>
</Show>
</Show> </Show>
<div class="flex items-center gap-1 shrink-0"> <div class="flex items-center gap-1 shrink-0">
<TooltipKeybind <Show when={!home()}>
class={web() ? "hidden xl:flex shrink-0 ml-14" : "hidden xl:flex shrink-0 ml-2"} <TooltipKeybind
placement="bottom" class={web() ? "hidden xl:flex shrink-0 ml-14" : "hidden xl:flex shrink-0 ml-2"}
title={language.t("command.sidebar.toggle")} placement="bottom"
keybind={command.keybind("sidebar.toggle")} title={language.t("command.sidebar.toggle")}
> keybind={command.keybind("sidebar.toggle")}
<Button
variant="ghost"
class="group/sidebar-toggle titlebar-icon w-8 h-6 p-0 box-border"
onClick={layout.sidebar.toggle}
aria-label={language.t("command.sidebar.toggle")}
aria-expanded={layout.sidebar.opened()}
> >
<Icon size="small" name={layout.sidebar.opened() ? "sidebar-active" : "sidebar"} /> <Button
</Button> variant="ghost"
</TooltipKeybind> class="group/sidebar-toggle titlebar-icon w-8 h-6 p-0 box-border"
<div class="hidden xl:flex items-center shrink-0"> onClick={layout.sidebar.toggle}
<Show when={params.dir}> aria-label={language.t("command.sidebar.toggle")}
<div aria-expanded={layout.sidebar.opened()}
class="flex items-center shrink-0 w-8 mr-1"
aria-hidden={layout.sidebar.opened() ? "true" : undefined}
> >
<Icon size="small" name={layout.sidebar.opened() ? "sidebar-active" : "sidebar"} />
</Button>
</TooltipKeybind>
<div class="hidden xl:flex items-center shrink-0">
<Show when={params.dir}>
<div <div
class="transition-opacity" class="flex items-center shrink-0 w-8 mr-1"
classList={{ aria-hidden={layout.sidebar.opened() ? "true" : undefined}
"opacity-100 duration-120 ease-out": !layout.sidebar.opened(),
"opacity-0 duration-120 ease-in delay-0 pointer-events-none": layout.sidebar.opened(),
}}
> >
<TooltipKeybind <div
placement="bottom" class="transition-opacity"
title={language.t("command.session.new")} classList={{
keybind={command.keybind("session.new")} "opacity-100 duration-120 ease-out": !layout.sidebar.opened(),
openDelay={2000} "opacity-0 duration-120 ease-in delay-0 pointer-events-none": layout.sidebar.opened(),
}}
> >
<Button <TooltipKeybind
variant="ghost" placement="bottom"
icon={creating() ? "new-session-active" : "new-session"} title={language.t("command.session.new")}
class="titlebar-icon w-8 h-6 p-0 box-border" keybind={command.keybind("session.new")}
disabled={layout.sidebar.opened()} openDelay={2000}
tabIndex={layout.sidebar.opened() ? -1 : undefined} >
onClick={() => { <Button
if (!params.dir) return variant="ghost"
navigate(`/${params.dir}/session`) icon={creating() ? "new-session-active" : "new-session"}
}} class="titlebar-icon w-8 h-6 p-0 box-border"
aria-label={language.t("command.session.new")} disabled={layout.sidebar.opened()}
aria-current={creating() ? "page" : undefined} tabIndex={layout.sidebar.opened() ? -1 : undefined}
/> onClick={() => {
</TooltipKeybind> if (!params.dir) return
navigate(`/${params.dir}/session`)
}}
aria-label={language.t("command.session.new")}
aria-current={creating() ? "page" : undefined}
/>
</TooltipKeybind>
</div>
</div> </div>
</Show>
<div
class="flex items-center gap-0 transition-transform"
classList={{
"translate-x-0": !layout.sidebar.opened(),
"-translate-x-[36px]": layout.sidebar.opened(),
"duration-180 ease-out": !layout.sidebar.opened(),
"duration-180 ease-in": layout.sidebar.opened(),
}}
>
<Tooltip placement="bottom" value={language.t("common.goBack")} openDelay={2000}>
<Button
variant="ghost"
icon="chevron-left"
class="titlebar-icon w-6 h-6 p-0 box-border"
disabled={!canBack()}
onClick={back}
aria-label={language.t("common.goBack")}
/>
</Tooltip>
<Tooltip placement="bottom" value={language.t("common.goForward")} openDelay={2000}>
<Button
variant="ghost"
icon="chevron-right"
class="titlebar-icon w-6 h-6 p-0 box-border"
disabled={!canForward()}
onClick={forward}
aria-label={language.t("common.goForward")}
/>
</Tooltip>
</div> </div>
</Show>
<div
class="flex items-center gap-0 transition-transform"
classList={{
"translate-x-0": !layout.sidebar.opened(),
"-translate-x-[36px]": layout.sidebar.opened(),
"duration-180 ease-out": !layout.sidebar.opened(),
"duration-180 ease-in": layout.sidebar.opened(),
}}
>
<Tooltip placement="bottom" value={language.t("common.goBack")} openDelay={2000}>
<Button
variant="ghost"
icon="chevron-left"
class="titlebar-icon w-6 h-6 p-0 box-border"
disabled={!canBack()}
onClick={back}
aria-label={language.t("common.goBack")}
/>
</Tooltip>
<Tooltip placement="bottom" value={language.t("common.goForward")} openDelay={2000}>
<Button
variant="ghost"
icon="chevron-right"
class="titlebar-icon w-6 h-6 p-0 box-border"
disabled={!canForward()}
onClick={forward}
aria-label={language.t("common.goForward")}
/>
</Tooltip>
</div> </div>
</div> </Show>
</div> </div>
<div id="opencode-titlebar-left" class="flex items-center gap-3 min-w-0 px-2" /> <div id="opencode-titlebar-left" class="flex items-center gap-3 min-w-0 px-2" />
</div> </div>

View File

@ -111,6 +111,7 @@ export default function Layout(props: ParentProps) {
let scrollContainerRef: HTMLDivElement | undefined let scrollContainerRef: HTMLDivElement | undefined
const params = useParams() const params = useParams()
const home = createMemo(() => !params.dir)
const globalSDK = useGlobalSDK() const globalSDK = useGlobalSDK()
const globalSync = useGlobalSync() const globalSync = useGlobalSync()
const layout = useLayout() const layout = useLayout()
@ -2241,7 +2242,7 @@ export default function Layout(props: ParentProps) {
const sidebarContent = (mobile?: boolean) => ( const sidebarContent = (mobile?: boolean) => (
<SidebarContent <SidebarContent
mobile={mobile} mobile={mobile}
opened={() => layout.sidebar.opened()} opened={() => (home() ? false : layout.sidebar.opened())}
aimMove={aim.move} aimMove={aim.move}
projects={projects} projects={projects}
renderProject={(project) => ( renderProject={(project) => (
@ -2285,7 +2286,7 @@ export default function Layout(props: ParentProps) {
"absolute inset-y-0 left-0": true, "absolute inset-y-0 left-0": true,
"z-10": true, "z-10": true,
}} }}
style={{ width: `${Math.max(layout.sidebar.width(), 244)}px` }} style={{ width: home() ? "4rem" : `${Math.max(layout.sidebar.width(), 244)}px` }}
ref={(el) => { ref={(el) => {
setState("nav", el) setState("nav", el)
}} }}
@ -2294,13 +2295,14 @@ export default function Layout(props: ParentProps) {
}} }}
onMouseLeave={() => { onMouseLeave={() => {
aim.reset() aim.reset()
if (home()) return
if (!sidebarHovering()) return if (!sidebarHovering()) return
arm() arm()
}} }}
> >
<div class="@container w-full h-full contain-strict">{sidebarContent()}</div> <div class="@container w-full h-full contain-strict">{sidebarContent()}</div>
<Show when={layout.sidebar.opened()}> <Show when={!home() && layout.sidebar.opened()}>
<div onPointerDown={() => setState("sizing", true)}> <div onPointerDown={() => setState("sizing", true)}>
<ResizeHandle <ResizeHandle
direction="horizontal" direction="horizontal"
@ -2325,46 +2327,54 @@ export default function Layout(props: ParentProps) {
style={{ left: "calc(4rem + 12px)" }} style={{ left: "calc(4rem + 12px)" }}
/> />
<div class="xl:hidden"> <Show when={!home()}>
<div <div class="xl:hidden">
classList={{ <div
"fixed inset-x-0 top-10 bottom-0 z-40 transition-opacity duration-200": true, classList={{
"opacity-100 pointer-events-auto": layout.mobileSidebar.opened(), "fixed inset-x-0 top-10 bottom-0 z-40 transition-opacity duration-200": true,
"opacity-0 pointer-events-none": !layout.mobileSidebar.opened(), "opacity-100 pointer-events-auto": layout.mobileSidebar.opened(),
}} "opacity-0 pointer-events-none": !layout.mobileSidebar.opened(),
onClick={(e) => { }}
if (e.target === e.currentTarget) layout.mobileSidebar.hide() onClick={(e) => {
}} if (e.target === e.currentTarget) layout.mobileSidebar.hide()
/> }}
<nav />
aria-label={language.t("sidebar.nav.projectsAndSessions")} <nav
data-component="sidebar-nav-mobile" aria-label={language.t("sidebar.nav.projectsAndSessions")}
classList={{ data-component="sidebar-nav-mobile"
"@container fixed top-10 bottom-0 left-0 z-50 w-full max-w-[400px] overflow-hidden border-r border-border-weaker-base bg-background-base transition-transform duration-200 ease-out": true, classList={{
"translate-x-0": layout.mobileSidebar.opened(), "@container fixed top-10 bottom-0 left-0 z-50 w-full max-w-[400px] overflow-hidden border-r border-border-weaker-base bg-background-base transition-transform duration-200 ease-out": true,
"-translate-x-full": !layout.mobileSidebar.opened(), "translate-x-0": layout.mobileSidebar.opened(),
}} "-translate-x-full": !layout.mobileSidebar.opened(),
onClick={(e) => e.stopPropagation()} }}
> onClick={(e) => e.stopPropagation()}
{sidebarContent(true)} >
</nav> {sidebarContent(true)}
</div> </nav>
</div>
</Show>
<div <div
classList={{ classList={{
"absolute inset-0": true, "absolute inset-0": true,
"xl:inset-y-0 xl:right-0 xl:left-[var(--main-left)]": true, "xl:inset-y-0 xl:right-0": true,
"xl:left-[var(--main-left)]": true,
"z-20": true, "z-20": true,
"transition-[left] duration-200 ease-[cubic-bezier(0.22,1,0.36,1)] will-change-[left] motion-reduce:transition-none": "transition-[left] duration-200 ease-[cubic-bezier(0.22,1,0.36,1)] will-change-[left] motion-reduce:transition-none":
!state.sizing, !state.sizing,
}} }}
style={{ style={{
"--main-left": layout.sidebar.opened() ? `${Math.max(layout.sidebar.width(), 244)}px` : "4rem", "--main-left": home()
? "4rem"
: layout.sidebar.opened()
? `${Math.max(layout.sidebar.width(), 244)}px`
: "4rem",
}} }}
> >
<main <main
classList={{ classList={{
"size-full overflow-x-hidden flex flex-col items-start contain-strict border-t border-border-weak-base bg-background-base xl:border-l xl:rounded-tl-[12px]": true, "size-full overflow-x-hidden flex flex-col items-start contain-strict border-t border-border-weak-base bg-background-base": true,
"xl:border-l xl:rounded-tl-[12px]": true,
}} }}
> >
<Show when={!autoselecting()} fallback={<div class="size-full" />}> <Show when={!autoselecting()} fallback={<div class="size-full" />}>
@ -2373,43 +2383,47 @@ export default function Layout(props: ParentProps) {
</main> </main>
</div> </div>
<div <Show when={!home()}>
classList={{ <div
"hidden xl:flex absolute inset-y-0 left-16 z-30": true, classList={{
"opacity-100 translate-x-0 pointer-events-auto": state.peeked && !layout.sidebar.opened(), "hidden xl:flex absolute inset-y-0 left-16 z-30": true,
"opacity-0 -translate-x-2 pointer-events-none": !state.peeked || layout.sidebar.opened(), "opacity-100 translate-x-0 pointer-events-auto": state.peeked && !layout.sidebar.opened(),
"transition-[opacity,transform] motion-reduce:transition-none": true, "opacity-0 -translate-x-2 pointer-events-none": !state.peeked || layout.sidebar.opened(),
"duration-180 ease-out": state.peeked && !layout.sidebar.opened(), "transition-[opacity,transform] motion-reduce:transition-none": true,
"duration-120 ease-in": !state.peeked || layout.sidebar.opened(), "duration-180 ease-out": state.peeked && !layout.sidebar.opened(),
}} "duration-120 ease-in": !state.peeked || layout.sidebar.opened(),
onMouseMove={disarm} }}
onMouseEnter={() => { onMouseMove={disarm}
disarm() onMouseEnter={() => {
aim.reset() disarm()
}} aim.reset()
onPointerDown={disarm} }}
onMouseLeave={() => { onPointerDown={disarm}
arm() onMouseLeave={() => {
}} arm()
> }}
<Show when={peekProject()}> >
<SidebarPanel project={peekProject} merged={false} /> <Show when={peekProject()}>
</Show> <SidebarPanel project={peekProject} merged={false} />
</div> </Show>
</div>
</Show>
<div <Show when={!home()}>
classList={{ <div
"hidden xl:block pointer-events-none absolute inset-y-0 right-0 z-25 overflow-hidden": true, classList={{
"opacity-100 translate-x-0": state.peeked && !layout.sidebar.opened(), "hidden xl:block pointer-events-none absolute inset-y-0 right-0 z-25 overflow-hidden": true,
"opacity-0 -translate-x-2": !state.peeked || layout.sidebar.opened(), "opacity-100 translate-x-0": state.peeked && !layout.sidebar.opened(),
"transition-[opacity,transform] motion-reduce:transition-none": true, "opacity-0 -translate-x-2": !state.peeked || layout.sidebar.opened(),
"duration-180 ease-out": state.peeked && !layout.sidebar.opened(), "transition-[opacity,transform] motion-reduce:transition-none": true,
"duration-120 ease-in": !state.peeked || layout.sidebar.opened(), "duration-180 ease-out": state.peeked && !layout.sidebar.opened(),
}} "duration-120 ease-in": !state.peeked || layout.sidebar.opened(),
style={{ left: `calc(4rem + ${Math.max(Math.max(layout.sidebar.width(), 244) - 64, 0)}px)` }} }}
> style={{ left: `calc(4rem + ${Math.max(Math.max(layout.sidebar.width(), 244) - 64, 0)}px)` }}
<div class="h-full w-px" style={{ "box-shadow": "var(--shadow-sidebar-overlay)" }} /> >
</div> <div class="h-full w-px" style={{ "box-shadow": "var(--shadow-sidebar-overlay)" }} />
</div>
</Show>
</div> </div>
</div> </div>
{import.meta.env.DEV && <DebugBar />} {import.meta.env.DEV && <DebugBar />}