feat: mobile opens posts as a scrolling feed instead of a modal
Docker Build and Publish / build-and-push (push) Failing after 10s
Docker Build and Publish / build-and-push (push) Failing after 10s
Tapping a post on a phone now opens a real feed page — header, media, actions, caption, next post peeking in below — scrolled with the browser's own vertical scrolling rather than swipe gestures. Desktop keeps the modal, where a centred sheet with side arrows suits a pointer. Only a window of posts is mounted: a profile here holds up to 1129 posts and mounting them all would mean as many full-size images. The window grows in both directions as you scroll. Growing upwards shifts everything below it, so the scroll offset is corrected in the same frame, before paint — measured against the real archive, an anchored post moves exactly one screen per scroll with no jump. Only the post crossing the viewport centre plays its video; the rest stay paused, so a feed of reels doesn't play ten at once. The URL tracks that same post, so scrolling updates /<archive>/p/<shortcode>/ the way Instagram does, and the back button returns to the grid with its scroll position intact. Feed video sizes to the container width rather than its intrinsic size: a <video> reports 300x150 until metadata loads, which made it render narrow and then jump to full width. It also gets a taller height ceiling than the modal so ordinary portrait media fills the width instead of sitting in side bars. The carousel is extracted into a shared MediaCarousel used by both surfaces, so horizontal paging behaves identically; touch-action keeps vertical scrolling passing through to the feed. PostModal loses its now-dead mobile swipe branch. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011uBWhwV3wFQ5MBCcMHHem7
This commit is contained in:
co-authored by
Claude Opus 5
parent
877d21ff1f
commit
c54f8d5b09
@@ -126,44 +126,12 @@ export const PostModal: React.FC<PostModalProps> = ({
|
||||
};
|
||||
const swipePower = (offset: number, velocity: number) => Math.abs(offset) * velocity;
|
||||
|
||||
/** Gesture navigation is touch-shaped below md; above it the arrows do the job. */
|
||||
const [isMobile, setIsMobile] = useState(
|
||||
() => typeof window !== 'undefined' && window.matchMedia('(max-width: 767px)').matches,
|
||||
);
|
||||
useEffect(() => {
|
||||
const query = window.matchMedia('(max-width: 767px)');
|
||||
const onChange = (e: MediaQueryListEvent) => setIsMobile(e.matches);
|
||||
query.addEventListener('change', onChange);
|
||||
return () => query.removeEventListener('change', onChange);
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* Vertical swipe moves between posts on touch, matching Instagram: horizontal
|
||||
* belongs to the carousel and only the carousel, so reaching the last slide
|
||||
* no longer flings you into the next post.
|
||||
*
|
||||
* Swiping down on the first post falls back to dismissing, which keeps the
|
||||
* familiar drag-to-close gesture available where it can't mean "previous".
|
||||
* Desktop-only surface: mobile opens PostFeed instead, so the only vertical
|
||||
* gesture left here is drag-to-dismiss.
|
||||
*/
|
||||
const SWIPE_DISTANCE = 90;
|
||||
const SWIPE_POWER = 8000;
|
||||
|
||||
const handleVerticalDragEnd = (offset: { y: number }, velocity: { y: number }) => {
|
||||
const power = swipePower(offset.y, velocity.y);
|
||||
|
||||
if (!isMobile) {
|
||||
if (offset.y > 200 || velocity.y > 800) onClose();
|
||||
return;
|
||||
}
|
||||
|
||||
const swipedUp = offset.y < -SWIPE_DISTANCE || power < -SWIPE_POWER;
|
||||
const swipedDown = offset.y > SWIPE_DISTANCE || power > SWIPE_POWER;
|
||||
|
||||
if (swipedUp && hasNextPost) goToPost(1, 'y', velocity.y);
|
||||
else if (swipedDown) {
|
||||
if (hasPrevPost) goToPost(-1, 'y', velocity.y);
|
||||
else onClose();
|
||||
}
|
||||
if (offset.y > 200 || velocity.y > 800) onClose();
|
||||
};
|
||||
|
||||
/*
|
||||
|
||||
Reference in New Issue
Block a user