import React, { useEffect, useState } from 'react'; import { ChevronLeft, ChevronRight } from 'lucide-react'; import { motion, AnimatePresence } from 'motion/react'; import { Post } from '../types'; import { cn } from '../lib/utils'; import { NAVIGATE, prefersReducedMotion, withVelocity } from '../lib/motion'; import { MediaRenderer } from './MediaRenderer'; interface MediaCarouselProps { post: Post; /** Pause video even when this slide is on screen (feed: only one plays). */ paused?: boolean; /** Override the media height ceiling (the feed allows taller media). */ heightCap?: string; /** Size video to the container width (see MediaRenderer). */ fillWidth?: boolean; className?: string; } /** * The horizontal slide strip for one post. * * Shared by the desktop modal and the mobile feed so a carousel behaves the * same in both. Horizontal drag belongs to the carousel and never navigates * between posts — vertical movement is the page's to handle. */ export const MediaCarousel: React.FC = ({ post, paused, heightCap, fillWidth, className }) => { const [index, setIndex] = useState(0); const [slide, setSlide] = useState<{ dir: number; velocity: number }>({ dir: 0, velocity: 0 }); const reduceMotion = prefersReducedMotion(); useEffect(() => setIndex(0), [post.id]); const paginate = (dir: number, velocity = 0) => { const next = index + dir; if (next < 0 || next >= post.media.length) return; setSlide({ dir, velocity }); setIndex(next); }; const variants = { enter: (d: number) => ({ x: d > 0 ? '100%' : '-100%', opacity: 1, zIndex: 0 }), center: { x: 0, opacity: 1, zIndex: 1 }, exit: (d: number) => ({ x: d < 0 ? '100%' : '-100%', opacity: 1, zIndex: 0 }), }; const swipePower = (offset: number, velocity: number) => Math.abs(offset) * velocity; const current = post.media[index]; return (
1 ? 'x' : false} dragDirectionLock dragConstraints={{ left: 0, right: 0 }} dragElastic={0.5} onDragEnd={(e, { offset, velocity }) => { const power = swipePower(offset.x, velocity.x); if (power < -15000) paginate(1, velocity.x); else if (power > 15000) paginate(-1, velocity.x); }} className="col-start-1 row-start-1 w-full flex items-center justify-center relative touch-pan-y" > {current && }
{post.media.length > 1 && ( <> {index > 0 && ( )} {index < post.media.length - 1 && ( )}
{post.media.map((_, i) => (
))}
)}
); };