Compare commits

...

1 Commits

Author SHA1 Message Date
David Hill 2451ea7303 update(ui): hide sidebar when no projects 2026-03-17 15:05:10 +00:00
4 changed files with 142 additions and 86 deletions
+39
View File
@@ -19,3 +19,42 @@ test("server picker dialog opens from home", async ({ page }) => {
await expect(dialog).toBeVisible() await expect(dialog).toBeVisible()
await expect(dialog.getByRole("textbox").first()).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()
})
+3
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
@@ -198,6 +199,7 @@ export function Titlebar() {
/> />
</div> </div>
</Show> </Show>
<Show when={!home()}>
<div class="flex items-center gap-1 shrink-0"> <div class="flex items-center gap-1 shrink-0">
<TooltipKeybind <TooltipKeybind
class={web() ? "hidden xl:flex shrink-0 ml-14" : "hidden xl:flex shrink-0 ml-2"} class={web() ? "hidden xl:flex shrink-0 ml-14" : "hidden xl:flex shrink-0 ml-2"}
@@ -283,6 +285,7 @@ export function Titlebar() {
</div> </div>
</div> </div>
</div> </div>
</Show>
<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>
+11 -1
View File
@@ -200,13 +200,20 @@ export default function Layout(props: ParentProps) {
onMount(() => { onMount(() => {
const stop = () => setState("sizing", false) const stop = () => setState("sizing", false)
const sync = () => {
if (!window.matchMedia("(min-width: 1280px)").matches) return
layout.mobileSidebar.hide()
}
window.addEventListener("pointerup", stop) window.addEventListener("pointerup", stop)
window.addEventListener("pointercancel", stop) window.addEventListener("pointercancel", stop)
window.addEventListener("blur", stop) window.addEventListener("blur", stop)
window.addEventListener("resize", sync)
sync()
onCleanup(() => { onCleanup(() => {
window.removeEventListener("pointerup", stop) window.removeEventListener("pointerup", stop)
window.removeEventListener("pointercancel", stop) window.removeEventListener("pointercancel", stop)
window.removeEventListener("blur", stop) window.removeEventListener("blur", stop)
window.removeEventListener("resize", sync)
}) })
}) })
@@ -2242,6 +2249,7 @@ export default function Layout(props: ParentProps) {
<SidebarContent <SidebarContent
mobile={mobile} mobile={mobile}
opened={() => layout.sidebar.opened()} opened={() => layout.sidebar.opened()}
hasPanel={() => !!currentProject()}
aimMove={aim.move} aimMove={aim.move}
projects={projects} projects={projects}
renderProject={(project) => ( renderProject={(project) => (
@@ -2340,7 +2348,9 @@ export default function Layout(props: ParentProps) {
aria-label={language.t("sidebar.nav.projectsAndSessions")} aria-label={language.t("sidebar.nav.projectsAndSessions")}
data-component="sidebar-nav-mobile" data-component="sidebar-nav-mobile"
classList={{ 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-0": layout.mobileSidebar.opened(),
"-translate-x-full": !layout.mobileSidebar.opened(), "-translate-x-full": !layout.mobileSidebar.opened(),
}} }}
@@ -15,6 +15,7 @@ import { type LocalProject } from "@/context/layout"
export const SidebarContent = (props: { export const SidebarContent = (props: {
mobile?: boolean mobile?: boolean
opened: Accessor<boolean> opened: Accessor<boolean>
hasPanel?: Accessor<boolean>
aimMove: (event: MouseEvent) => void aimMove: (event: MouseEvent) => void
projects: Accessor<LocalProject[]> projects: Accessor<LocalProject[]>
renderProject: (project: LocalProject) => JSX.Element renderProject: (project: LocalProject) => JSX.Element
@@ -33,6 +34,7 @@ export const SidebarContent = (props: {
renderPanel: () => JSX.Element renderPanel: () => JSX.Element
}): JSX.Element => { }): JSX.Element => {
const expanded = createMemo(() => !!props.mobile || props.opened()) const expanded = createMemo(() => !!props.mobile || props.opened())
const hasPanel = createMemo(() => props.hasPanel?.() ?? true)
const placement = () => (props.mobile ? "bottom" : "right") const placement = () => (props.mobile ? "bottom" : "right")
let panel: HTMLDivElement | undefined let panel: HTMLDivElement | undefined
@@ -111,6 +113,7 @@ export const SidebarContent = (props: {
</div> </div>
</div> </div>
<Show when={hasPanel()}>
<div <div
ref={(el) => { ref={(el) => {
panel = el panel = el
@@ -120,6 +123,7 @@ export const SidebarContent = (props: {
> >
{props.renderPanel()} {props.renderPanel()}
</div> </div>
</Show>
</div> </div>
) )
} }