Sfoglia il codice sorgente

tweak(ui): add an empty state to the sidebar when no projects (#17971)

Co-authored-by: Shoubhit Dash <[email protected]>
David Hill 1 mese fa
parent
commit
7daea69e13

+ 3 - 0
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()
 })
 

+ 33 - 30
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" />

+ 2 - 0
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",

+ 22 - 8
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 />
       }
     />
   )