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
|
- 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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
})
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue