diff --git a/apps/client/src/widgets/mobile_widgets/MobileNoteNavigator.css b/apps/client/src/widgets/mobile_widgets/MobileNoteNavigator.css index 1f27b06f51..bf2e7b07fd 100644 --- a/apps/client/src/widgets/mobile_widgets/MobileNoteNavigator.css +++ b/apps/client/src/widgets/mobile_widgets/MobileNoteNavigator.css @@ -35,8 +35,7 @@ padding-bottom: env(safe-area-inset-bottom, 0); } -.mobile-navigator-scroll.is-pending > .mobile-navigator-current-tile, -.mobile-navigator-scroll.is-pending > .mobile-navigator-list { +.mobile-navigator-scroll.is-pending .mobile-navigator-body { visibility: hidden; } diff --git a/apps/client/src/widgets/mobile_widgets/MobileNoteNavigator.tsx b/apps/client/src/widgets/mobile_widgets/MobileNoteNavigator.tsx index 648f56d687..002a05153c 100644 --- a/apps/client/src/widgets/mobile_widgets/MobileNoteNavigator.tsx +++ b/apps/client/src/widgets/mobile_widgets/MobileNoteNavigator.tsx @@ -1,7 +1,7 @@ import "./MobileNoteNavigator.css"; import clsx from "clsx"; -import { useCallback, useEffect, useMemo, useRef, useState } from "preact/hooks"; +import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "preact/hooks"; import type FNote from "../../entities/fnote"; import froca from "../../services/froca"; @@ -114,6 +114,9 @@ export default function MobileNoteNavigator() { }, [pendingNote]); const scrollRef = useRef(null); + const bodyRef = useRef(null); + const directionRef = useRef<"forward" | "backward" | null>(null); + const [commitCounter, setCommitCounter] = useState(0); useEffect(() => { if (!nextStack || !pendingId || nextReadyNoteId !== pendingId) return; @@ -121,10 +124,27 @@ export default function MobileNoteNavigator() { setReadyForNoteId(pendingId); setNextStack(null); setNextReadyNoteId(null); + setCommitCounter((c) => c + 1); // Reset scroll so the committed tile is visible at the top of the column. if (scrollRef.current) scrollRef.current.scrollTop = 0; }, [nextStack, pendingId, nextReadyNoteId]); + // Brief slide-in on forward / back so the swap feels directional without blocking the user. + useLayoutEffect(() => { + if (commitCounter === 0) return; + const direction = directionRef.current; + directionRef.current = null; + if (!direction || !bodyRef.current) return; + const offset = direction === "forward" ? "60%" : "-60%"; + bodyRef.current.animate( + [ + { transform: `translateX(${offset})`, opacity: 0.4 }, + { transform: "translateX(0)", opacity: 1 } + ], + { duration: 180, easing: "ease-out" } + ); + }, [commitCounter]); + const navigateTo = useCallback( (newStack: string[]) => { if (hasCommittedOnceRef.current) { @@ -139,6 +159,7 @@ export default function MobileNoteNavigator() { const goBack = useCallback(() => { if (stack.length <= 1) return; manualStackRef.current = true; + directionRef.current = "backward"; navigateTo(stack.slice(0, -1)); }, [stack, navigateTo]); @@ -159,6 +180,7 @@ export default function MobileNoteNavigator() { const drillInto = useCallback( (childNotePath: string) => { manualStackRef.current = true; + directionRef.current = "forward"; navigateTo([...stack, childNotePath]); }, [stack, navigateTo] @@ -179,51 +201,53 @@ export default function MobileNoteNavigator() {
- {parentNote && ( -
-
- - {parentNote.title} - +
+ {parentNote && ( +
+
+ + {parentNote.title} + +
+
+ +
-
- -
-
- )} - -
- {!isLoaded || !parentNote ? null : children.length === 0 ? ( - - ) : ( - children.map((child) => ( - - )) )} + +
+ {!isLoaded || !parentNote ? null : children.length === 0 ? ( + + ) : ( + children.map((child) => ( + + )) + )} +
{showInitialLoader && (