Keep the space menu open when a space is picked from its rail

This commit is contained in:
Coracle-Bot 2026-09-09 19:24:44 +00:00
parent 16cc2109d5
commit 68a4eee8ba
5 changed files with 57 additions and 4 deletions

View file

@ -194,6 +194,7 @@ return a `Command`, so `.then(publish)` is usually all you need.
- Use `$state.snapshot` if calling component might unmount - Use `$state.snapshot` if calling component might unmount
- Navigate with `navigate` from `app/modal.ts` rather than `goto` — an open modal owns the current - Navigate with `navigate` from `app/modal.ts` rather than `goto` — an open modal owns the current
history entry, so a navigation that drops it replaces that entry instead of stacking on it history entry, so a navigation that drops it replaces that entry instead of stacking on it
- Pass `keepModal` to `navigate` to change the page under a modal and leave it open
## Development Workflow ## Development Workflow

View file

@ -146,3 +146,39 @@ test("takes over the space menu's history entry when you navigate out of it", as
await expect(page).toHaveURL(new RegExp(`${roomPath(space.url, "lounge")}$`)) await expect(page).toHaveURL(new RegExp(`${roomPath(space.url, "lounge")}$`))
await expect(drawer).toHaveCount(0) await expect(drawer).toHaveCount(0)
}) })
test("switches spaces inside the space menu without closing it", async ({seed, as}) => {
const scenario = await seed(({relay, user}) => {
const space = relay("space")
const other = relay("other")
space.room("lounge", {name: "Space Lounge"})
other.room("garden", {name: "Other Garden"})
space.join(user.alice, "lounge")
other.join(user.alice, "garden")
})
const space = scenario.space("space")
const other = scenario.space("other")
const page = await as(users.alice, roomPath(space.url, "lounge"), {
context: {viewport: {width: 390, height: 844}, hasTouch: true},
})
const drawer = page.locator(".drawer")
await page.getByRole("button", {name: "Open space menu"}).click()
await expect(drawer.getByRole("link", {name: "Space Lounge"})).toBeVisible()
// The rail is in the menu on a phone, so a space is picked with the menu still open and the room
// is picked after it. Navigating out from under the menu would close it on the first tap.
await drawer.locator('.primary-nav [data-tip^="other"]').click()
await expect(page).toHaveURL(new RegExp(spacePath(other.url)))
await expect(drawer.getByRole("link", {name: "Other Garden"})).toBeVisible()
await drawer.getByRole("link", {name: "Other Garden"}).click()
await expect(page).toHaveURL(new RegExp(`${roomPath(other.url, "garden")}$`))
await expect(drawer).toHaveCount(0)
})

View file

@ -17,7 +17,7 @@
const onClick = () => { const onClick = () => {
const entryPath = makeSpaceEntryPath(url) const entryPath = makeSpaceEntryPath(url)
navigate(entryPath, {replaceState: entryPath === $page.url.pathname}) navigate(entryPath, {keepModal: true, replaceState: entryPath === $page.url.pathname})
} }
const path = makeSpacePath(url) const path = makeSpacePath(url)

View file

@ -3,6 +3,7 @@
import SecondaryNav from "@lib/components/SecondaryNav.svelte" import SecondaryNav from "@lib/components/SecondaryNav.svelte"
import SpaceMenu from "@app/components/SpaceMenu.svelte" import SpaceMenu from "@app/components/SpaceMenu.svelte"
import PrimaryNavSpaces from "@app/components/PrimaryNavSpaces.svelte" import PrimaryNavSpaces from "@app/components/PrimaryNavSpaces.svelte"
import {lastSpaceUrl} from "@app/routes"
type Props = { type Props = {
url: string url: string
@ -10,6 +11,8 @@
const {url}: Props = $props() const {url}: Props = $props()
const activeUrl = $derived($lastSpaceUrl ?? url)
const duration = 200 const duration = 200
const parallax = 10 const parallax = 10
@ -32,7 +35,9 @@
</div> </div>
<div class="flex w-0 min-w-0 grow" in:translate={slide(parallax)} out:translate={slide(0)}> <div class="flex w-0 min-w-0 grow" in:translate={slide(parallax)} out:translate={slide(0)}>
<SecondaryNav visible class="secondary-nav--mobile-space mt-0 h-full w-0 min-w-0 grow pb-0"> <SecondaryNav visible class="secondary-nav--mobile-space mt-0 h-full w-0 min-w-0 grow pb-0">
<SpaceMenu {url} mobile /> {#key activeUrl}
<SpaceMenu url={activeUrl} mobile />
{/key}
</SecondaryNav> </SecondaryNav>
</div> </div>
</div> </div>

View file

@ -64,8 +64,19 @@ const closesModal = (path: string) => {
return hash !== "" && !path.endsWith(hash) return hash !== "" && !path.endsWith(hash)
} }
export const navigate = (path: string, options?: Parameters<typeof goto>[1]) => export type NavigateOptions = Parameters<typeof goto>[1] & {keepModal?: boolean}
goto(path, {...options, replaceState: options?.replaceState || closesModal(path)})
// The modal hash is written with the History API, so it never reaches `page` — carrying it onto
// the path is what navigates underneath an open modal rather than closing it
export const navigate = (path: string, {keepModal, ...options}: NavigateOptions = {}) => {
const hash = get(modalHash)
if (keepModal && hash) {
return goto(path + hash, {...options, replaceState: true})
}
return goto(path, {...options, replaceState: options.replaceState || closesModal(path)})
}
export const modalStack = deriveDeduplicated([modalHash, modals], ([$hash, $modals]) => { export const modalStack = deriveDeduplicated([modalHash, modals], ([$hash, $modals]) => {
return getIdsFromHash($hash) return getIdsFromHash($hash)