diff options
| author | David Hill <[email protected]> | 2026-03-17 19:54:14 +0000 |
|---|---|---|
| committer | GitHub <[email protected]> | 2026-03-17 19:54:14 +0000 |
| commit | 7daea69e13e5a17278fe244273fdeb141b0369d6 (patch) | |
| tree | 4d421611992fb686a052b72ff928711f6805dc35 | |
| parent | 0772a9591807d15c369c37edc01b9018bdc6e7d1 (diff) | |
| download | opencode-7daea69e13e5a17278fe244273fdeb141b0369d6.tar.gz opencode-7daea69e13e5a17278fe244273fdeb141b0369d6.zip | |
tweak(ui): add an empty state to the sidebar when no projects (#17971)
Co-authored-by: Shoubhit Dash <[email protected]>
| -rw-r--r-- | packages/app/e2e/app/home.spec.ts | 3 | ||||
| -rw-r--r-- | packages/app/src/components/titlebar.tsx | 63 | ||||
| -rw-r--r-- | packages/app/src/i18n/en.ts | 2 | ||||
| -rw-r--r-- | packages/app/src/pages/layout.tsx | 30 |
4 files changed, 60 insertions, 38 deletions
diff --git a/packages/app/e2e/app/home.spec.ts b/packages/app/e2e/app/home.spec.ts index a3cedf7cb..5deba4300 100644 --- a/packages/app/e2e/app/home.spec.ts +++ b/packages/app/e2e/app/home.spec.ts @@ -3,8 +3,11 @@ import { serverNamePattern } from "../utils" test("home renders and shows core entrypoints", async ({ page }) => { await page.goto("/") + const nav = page.locator('[data-component="sidebar-nav-desktop"]') await expect(page.getByRole("button", { name: "Open project" }).first()).toBeVisible() + await expect(nav.getByText("No projects open")).toBeVisible() + await expect(nav.getByText("Open a project to get started")).toBeVisible() await expect(page.getByRole("button", { name: serverNamePattern })).toBeVisible() }) diff --git a/packages/app/src/components/titlebar.tsx b/packages/app/src/components/titlebar.tsx index 345903420..77de1a73c 100644 --- a/packages/app/src/components/titlebar.tsx +++ b/packages/app/src/components/titlebar.tsx @@ -77,6 +77,7 @@ export function Titlebar() { const canBack = createMemo(() => history.index > 0) const canForward = createMemo(() => history.index < history.stack.length - 1) + const hasProjects = createMemo(() => layout.projects.list().length > 0) const back = () => { const next = backPath(history) @@ -251,36 +252,38 @@ export function Titlebar() { </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> + <Show when={hasProjects()}> + <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> + </Show> </div> </div> <div id="opencode-titlebar-left" class="flex items-center gap-3 min-w-0 px-2" /> diff --git a/packages/app/src/i18n/en.ts b/packages/app/src/i18n/en.ts index ad12e1e0d..7f6816de9 100644 --- a/packages/app/src/i18n/en.ts +++ b/packages/app/src/i18n/en.ts @@ -674,6 +674,8 @@ export const dict = { "sidebar.project.recentSessions": "Recent sessions", "sidebar.project.viewAllSessions": "View all sessions", "sidebar.project.clearNotifications": "Clear notifications", + "sidebar.empty.title": "No projects open", + "sidebar.empty.description": "Open a project to get started", "debugBar.ariaLabel": "Development performance diagnostics", "debugBar.na": "n/a", diff --git a/packages/app/src/pages/layout.tsx b/packages/app/src/pages/layout.tsx index ab2687dca..a694ce094 100644 --- a/packages/app/src/pages/layout.tsx +++ b/packages/app/src/pages/layout.tsx @@ -1959,6 +1959,7 @@ export default function Layout(props: ParentProps) { const merged = createMemo(() => panelProps.mobile || (panelProps.merged ?? layout.sidebar.opened())) const hover = createMemo(() => !panelProps.mobile && panelProps.merged === false && !layout.sidebar.opened()) const popover = createMemo(() => !!panelProps.mobile || panelProps.merged === false || layout.sidebar.opened()) + const empty = createMemo(() => !params.dir && layout.projects.list().length === 0) const projectName = createMemo(() => { const item = project() if (!item) return "" @@ -2011,7 +2012,26 @@ export default function Layout(props: ParentProps) { width: panelProps.mobile ? undefined : `${Math.max(Math.max(layout.sidebar.width(), 244) - 64, 0)}px`, }} > - <Show when={project()}> + <Show + when={project()} + fallback={ + <Show when={empty()}> + <div class="flex-1 min-h-0 -mt-4 flex items-center justify-center px-6 pb-64 text-center"> + <div class="mt-8 flex max-w-60 flex-col items-center gap-6 text-center"> + <div class="flex flex-col gap-3"> + <div class="text-14-medium text-text-strong">{language.t("sidebar.empty.title")}</div> + <div class="text-14-regular text-text-base" style={{ "line-height": "var(--line-height-normal)" }}> + {language.t("sidebar.empty.description")} + </div> + </div> + <Button size="large" icon="folder-add-left" onClick={chooseProject}> + {language.t("command.project.open")} + </Button> + </div> + </div> + </Show> + } + > <> <div class="shrink-0 pl-1 py-1"> <div class="group/project flex items-start justify-between gap-2 py-2 pl-2 pr-0"> @@ -2260,13 +2280,7 @@ export default function Layout(props: ParentProps) { helpLabel={() => language.t("sidebar.help")} onOpenHelp={() => platform.openLink("https://opencode.ai/desktop-feedback")} renderPanel={() => - mobile ? ( - <SidebarPanel project={currentProject} mobile /> - ) : ( - <Show when={currentProject()}> - <SidebarPanel project={currentProject} merged /> - </Show> - ) + mobile ? <SidebarPanel project={currentProject} mobile /> : <SidebarPanel project={currentProject} merged /> } /> ) |
