Keep the space menu open when a space is picked from its rail
This commit is contained in:
parent
16cc2109d5
commit
68a4eee8ba
5 changed files with 57 additions and 4 deletions
|
|
@ -194,6 +194,7 @@ return a `Command`, so `.then(publish)` is usually all you need.
|
|||
- Use `$state.snapshot` if calling component might unmount
|
||||
- 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
|
||||
- Pass `keepModal` to `navigate` to change the page under a modal and leave it open
|
||||
|
||||
## Development Workflow
|
||||
|
||||
|
|
|
|||
|
|
@ -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(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)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
const onClick = () => {
|
||||
const entryPath = makeSpaceEntryPath(url)
|
||||
|
||||
navigate(entryPath, {replaceState: entryPath === $page.url.pathname})
|
||||
navigate(entryPath, {keepModal: true, replaceState: entryPath === $page.url.pathname})
|
||||
}
|
||||
|
||||
const path = makeSpacePath(url)
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
import SecondaryNav from "@lib/components/SecondaryNav.svelte"
|
||||
import SpaceMenu from "@app/components/SpaceMenu.svelte"
|
||||
import PrimaryNavSpaces from "@app/components/PrimaryNavSpaces.svelte"
|
||||
import {lastSpaceUrl} from "@app/routes"
|
||||
|
||||
type Props = {
|
||||
url: string
|
||||
|
|
@ -10,6 +11,8 @@
|
|||
|
||||
const {url}: Props = $props()
|
||||
|
||||
const activeUrl = $derived($lastSpaceUrl ?? url)
|
||||
|
||||
const duration = 200
|
||||
|
||||
const parallax = 10
|
||||
|
|
@ -32,7 +35,9 @@
|
|||
</div>
|
||||
<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">
|
||||
<SpaceMenu {url} mobile />
|
||||
{#key activeUrl}
|
||||
<SpaceMenu url={activeUrl} mobile />
|
||||
{/key}
|
||||
</SecondaryNav>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -64,8 +64,19 @@ const closesModal = (path: string) => {
|
|||
return hash !== "" && !path.endsWith(hash)
|
||||
}
|
||||
|
||||
export const navigate = (path: string, options?: Parameters<typeof goto>[1]) =>
|
||||
goto(path, {...options, replaceState: options?.replaceState || closesModal(path)})
|
||||
export type NavigateOptions = Parameters<typeof goto>[1] & {keepModal?: boolean}
|
||||
|
||||
// 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]) => {
|
||||
return getIdsFromHash($hash)
|
||||
|
|
|
|||
Loading…
Reference in a new issue