Преглед на файлове

update(ui): hide sidebar when no projects

David Hill преди 1 месец
родител
ревизия
2451ea7303
променени са 4 файла, в които са добавени 142 реда и са изтрити 86 реда
  1. 39 0
      packages/app/e2e/app/home.spec.ts
  2. 79 76
      packages/app/src/components/titlebar.tsx
  3. 11 1
      packages/app/src/pages/layout.tsx
  4. 13 9
      packages/app/src/pages/layout/sidebar-shell.tsx

+ 39 - 0
packages/app/e2e/app/home.spec.ts

@@ -19,3 +19,42 @@ test("server picker dialog opens from home", async ({ page }) => {
   await expect(dialog).toBeVisible()
   await expect(dialog.getByRole("textbox").first()).toBeVisible()
 })
+
+test("home hides desktop history and sidebar controls", async ({ page }) => {
+  await page.setViewportSize({ width: 1400, height: 900 })
+  await page.goto("/")
+
+  await expect(page.getByRole("button", { name: "Toggle sidebar" })).toHaveCount(0)
+  await expect(page.getByRole("button", { name: "Go back" })).toHaveCount(0)
+  await expect(page.getByRole("button", { name: "Go forward" })).toHaveCount(0)
+  await expect(page.getByRole("button", { name: "Toggle menu" })).toHaveCount(0)
+})
+
+test("home keeps the mobile menu available", async ({ page }) => {
+  await page.setViewportSize({ width: 430, height: 900 })
+  await page.goto("/")
+
+  const toggle = page.getByRole("button", { name: "Toggle menu" }).first()
+  await expect(toggle).toBeVisible()
+  await toggle.click()
+
+  const nav = page.locator('[data-component="sidebar-nav-mobile"]')
+  await expect(nav).toBeVisible()
+  await expect.poll(async () => (await nav.boundingBox())?.width ?? 0).toBeLessThan(120)
+  await expect(nav.getByRole("button", { name: "Settings" })).toBeVisible()
+  await expect(nav.getByRole("button", { name: "Help" })).toBeVisible()
+
+  await page.setViewportSize({ width: 1400, height: 900 })
+  await expect(nav).toBeHidden()
+
+  await page.setViewportSize({ width: 430, height: 900 })
+  await expect(toggle).toBeVisible()
+  await expect(toggle).toHaveAttribute("aria-expanded", "false")
+  await expect(nav).toHaveClass(/-translate-x-full/)
+
+  await toggle.click()
+  await expect(nav).toBeVisible()
+
+  await nav.getByRole("button", { name: "Settings" }).click()
+  await expect(page.getByRole("dialog")).toBeVisible()
+})

+ 79 - 76
packages/app/src/components/titlebar.tsx

@@ -58,6 +58,7 @@ export function Titlebar() {
   })
 
   const path = () => `${location.pathname}${location.search}${location.hash}`
+  const home = createMemo(() => !params.dir)
   const creating = createMemo(() => {
     if (!params.dir) return false
     if (params.id) return false
@@ -198,91 +199,93 @@ export function Titlebar() {
             />
           </div>
         </Show>
-        <div class="flex items-center gap-1 shrink-0">
-          <TooltipKeybind
-            class={web() ? "hidden xl:flex shrink-0 ml-14" : "hidden xl:flex shrink-0 ml-2"}
-            placement="bottom"
-            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()}
+        <Show when={!home()}>
+          <div class="flex items-center gap-1 shrink-0">
+            <TooltipKeybind
+              class={web() ? "hidden xl:flex shrink-0 ml-14" : "hidden xl:flex shrink-0 ml-2"}
+              placement="bottom"
+              title={language.t("command.sidebar.toggle")}
+              keybind={command.keybind("sidebar.toggle")}
             >
-              <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
-                class="flex items-center shrink-0 w-8 mr-1"
-                aria-hidden={layout.sidebar.opened() ? "true" : undefined}
+              <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>
+            </TooltipKeybind>
+            <div class="hidden xl:flex items-center shrink-0">
+              <Show when={params.dir}>
                 <div
-                  class="transition-opacity"
-                  classList={{
-                    "opacity-100 duration-120 ease-out": !layout.sidebar.opened(),
-                    "opacity-0 duration-120 ease-in delay-0 pointer-events-none": layout.sidebar.opened(),
-                  }}
+                  class="flex items-center shrink-0 w-8 mr-1"
+                  aria-hidden={layout.sidebar.opened() ? "true" : undefined}
                 >
-                  <TooltipKeybind
-                    placement="bottom"
-                    title={language.t("command.session.new")}
-                    keybind={command.keybind("session.new")}
-                    openDelay={2000}
+                  <div
+                    class="transition-opacity"
+                    classList={{
+                      "opacity-100 duration-120 ease-out": !layout.sidebar.opened(),
+                      "opacity-0 duration-120 ease-in delay-0 pointer-events-none": layout.sidebar.opened(),
+                    }}
                   >
-                    <Button
-                      variant="ghost"
-                      icon={creating() ? "new-session-active" : "new-session"}
-                      class="titlebar-icon w-8 h-6 p-0 box-border"
-                      disabled={layout.sidebar.opened()}
-                      tabIndex={layout.sidebar.opened() ? -1 : undefined}
-                      onClick={() => {
-                        if (!params.dir) return
-                        navigate(`/${params.dir}/session`)
-                      }}
-                      aria-label={language.t("command.session.new")}
-                      aria-current={creating() ? "page" : undefined}
-                    />
-                  </TooltipKeybind>
+                    <TooltipKeybind
+                      placement="bottom"
+                      title={language.t("command.session.new")}
+                      keybind={command.keybind("session.new")}
+                      openDelay={2000}
+                    >
+                      <Button
+                        variant="ghost"
+                        icon={creating() ? "new-session-active" : "new-session"}
+                        class="titlebar-icon w-8 h-6 p-0 box-border"
+                        disabled={layout.sidebar.opened()}
+                        tabIndex={layout.sidebar.opened() ? -1 : undefined}
+                        onClick={() => {
+                          if (!params.dir) return
+                          navigate(`/${params.dir}/session`)
+                        }}
+                        aria-label={language.t("command.session.new")}
+                        aria-current={creating() ? "page" : undefined}
+                      />
+                    </TooltipKeybind>
+                  </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>
-            <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 id="opencode-titlebar-left" class="flex items-center gap-3 min-w-0 px-2" />
       </div>
 

+ 11 - 1
packages/app/src/pages/layout.tsx

@@ -200,13 +200,20 @@ export default function Layout(props: ParentProps) {
 
   onMount(() => {
     const stop = () => setState("sizing", false)
+    const sync = () => {
+      if (!window.matchMedia("(min-width: 1280px)").matches) return
+      layout.mobileSidebar.hide()
+    }
     window.addEventListener("pointerup", stop)
     window.addEventListener("pointercancel", stop)
     window.addEventListener("blur", stop)
+    window.addEventListener("resize", sync)
+    sync()
     onCleanup(() => {
       window.removeEventListener("pointerup", stop)
       window.removeEventListener("pointercancel", stop)
       window.removeEventListener("blur", stop)
+      window.removeEventListener("resize", sync)
     })
   })
 
@@ -2242,6 +2249,7 @@ export default function Layout(props: ParentProps) {
     <SidebarContent
       mobile={mobile}
       opened={() => layout.sidebar.opened()}
+      hasPanel={() => !!currentProject()}
       aimMove={aim.move}
       projects={projects}
       renderProject={(project) => (
@@ -2340,7 +2348,9 @@ export default function Layout(props: ParentProps) {
                 aria-label={language.t("sidebar.nav.projectsAndSessions")}
                 data-component="sidebar-nav-mobile"
                 classList={{
-                  "@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,
+                  "@container fixed top-10 bottom-0 left-0 z-50 overflow-hidden border-r border-border-weaker-base bg-background-base transition-transform duration-200 ease-out": true,
+                  "w-16": !currentProject(),
+                  "w-full max-w-[400px]": !!currentProject(),
                   "translate-x-0": layout.mobileSidebar.opened(),
                   "-translate-x-full": !layout.mobileSidebar.opened(),
                 }}

+ 13 - 9
packages/app/src/pages/layout/sidebar-shell.tsx

@@ -15,6 +15,7 @@ import { type LocalProject } from "@/context/layout"
 export const SidebarContent = (props: {
   mobile?: boolean
   opened: Accessor<boolean>
+  hasPanel?: Accessor<boolean>
   aimMove: (event: MouseEvent) => void
   projects: Accessor<LocalProject[]>
   renderProject: (project: LocalProject) => JSX.Element
@@ -33,6 +34,7 @@ export const SidebarContent = (props: {
   renderPanel: () => JSX.Element
 }): JSX.Element => {
   const expanded = createMemo(() => !!props.mobile || props.opened())
+  const hasPanel = createMemo(() => props.hasPanel?.() ?? true)
   const placement = () => (props.mobile ? "bottom" : "right")
   let panel: HTMLDivElement | undefined
 
@@ -111,15 +113,17 @@ export const SidebarContent = (props: {
         </div>
       </div>
 
-      <div
-        ref={(el) => {
-          panel = el
-        }}
-        classList={{ "flex-1 flex h-full min-h-0 min-w-0 overflow-hidden": true, "pointer-events-none": !expanded() }}
-        aria-hidden={!expanded()}
-      >
-        {props.renderPanel()}
-      </div>
+      <Show when={hasPanel()}>
+        <div
+          ref={(el) => {
+            panel = el
+          }}
+          classList={{ "flex-1 flex h-full min-h-0 min-w-0 overflow-hidden": true, "pointer-events-none": !expanded() }}
+          aria-hidden={!expanded()}
+        >
+          {props.renderPanel()}
+        </div>
+      </Show>
     </div>
   )
 }