feat: add mobile carousel view with auto-play for BentoReviews component
This commit is contained in:
@@ -2,8 +2,10 @@
|
|||||||
|
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { H2, P } from "@/components/Texts";
|
import { H2, P } from "@/components/Texts";
|
||||||
import React from "react";
|
import React, { useState, useEffect, useRef } from "react";
|
||||||
import { BentoGrid, MotionBentoGridItem } from "@/components/ui/bento-grid";
|
import { BentoGrid, MotionBentoGridItem } from "@/components/ui/bento-grid";
|
||||||
|
import { AnimatePresence, motion } from "framer-motion";
|
||||||
|
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||||
import { FadeIn } from "./FadeIn";
|
import { FadeIn } from "./FadeIn";
|
||||||
|
|
||||||
const items = [
|
const items = [
|
||||||
@@ -53,6 +55,42 @@ const items = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
export function BentoReviews() {
|
export function BentoReviews() {
|
||||||
|
const [activeIndex, setActiveIndex] = useState(0);
|
||||||
|
const [isPaused, setIsPaused] = useState(false);
|
||||||
|
const intervalRef = useRef<NodeJS.Timeout | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isPaused) {
|
||||||
|
intervalRef.current = setInterval(() => {
|
||||||
|
setActiveIndex((prevIndex) => (prevIndex + 1) % items.length);
|
||||||
|
}, 3000);
|
||||||
|
} else {
|
||||||
|
if (intervalRef.current) {
|
||||||
|
clearInterval(intervalRef.current);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (intervalRef.current) {
|
||||||
|
clearInterval(intervalRef.current);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [isPaused]);
|
||||||
|
|
||||||
|
const handleCardTap = () => {
|
||||||
|
setIsPaused(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePrev = () => {
|
||||||
|
setActiveIndex((prevIndex) => (prevIndex - 1 + items.length) % items.length);
|
||||||
|
setIsPaused(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleNext = () => {
|
||||||
|
setActiveIndex((prevIndex) => (prevIndex + 1) % items.length);
|
||||||
|
setIsPaused(true);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className="relative isolate pt-24 pb-12 bg-black text-center w-full lg:px-0 px-4">
|
<div className="relative isolate pt-24 pb-12 bg-black text-center w-full lg:px-0 px-4">
|
||||||
@@ -70,6 +108,9 @@ export function BentoReviews() {
|
|||||||
</div>
|
</div>
|
||||||
</FadeIn>
|
</FadeIn>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Desktop Grid */}
|
||||||
|
<div className="hidden lg:block">
|
||||||
<BentoGrid className="max-w-8xl lg:px-12 px-4 pb-12 lg:grid-cols-3">
|
<BentoGrid className="max-w-8xl lg:px-12 px-4 pb-12 lg:grid-cols-3">
|
||||||
{items.map((item, i) => (
|
{items.map((item, i) => (
|
||||||
<MotionBentoGridItem
|
<MotionBentoGridItem
|
||||||
@@ -88,6 +129,44 @@ export function BentoReviews() {
|
|||||||
))}
|
))}
|
||||||
</BentoGrid>
|
</BentoGrid>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Mobile Carousel */}
|
||||||
|
<div className="lg:hidden block px-4 pb-12">
|
||||||
|
<div className="relative h-[24rem] w-full overflow-hidden">
|
||||||
|
<div className="absolute inset-0" onTouchStart={handleCardTap} />
|
||||||
|
<AnimatePresence initial={false}>
|
||||||
|
<motion.div
|
||||||
|
key={activeIndex}
|
||||||
|
className="absolute h-full w-full"
|
||||||
|
initial={{ x: "100%", opacity: 0 }}
|
||||||
|
animate={{ x: 0, opacity: 1 }}
|
||||||
|
exit={{ x: "-100%", opacity: 0 }}
|
||||||
|
transition={{ type: "spring", stiffness: 300, damping: 30 }}
|
||||||
|
>
|
||||||
|
<MotionBentoGridItem
|
||||||
|
className="h-full"
|
||||||
|
title={items[activeIndex].title}
|
||||||
|
subtitle={items[activeIndex].subtitle}
|
||||||
|
description={items[activeIndex].description}
|
||||||
|
video={items[activeIndex].video}
|
||||||
|
/>
|
||||||
|
</motion.div>
|
||||||
|
</AnimatePresence>
|
||||||
|
<button
|
||||||
|
onClick={handlePrev}
|
||||||
|
className="absolute left-2 top-[58%] -translate-y-1/2 rounded-full bg-black/50 p-2 text-white z-10"
|
||||||
|
>
|
||||||
|
<ChevronLeft className="h-6 w-6" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={handleNext}
|
||||||
|
className="absolute right-2 top-[58%] -translate-y-1/2 rounded-full bg-black/50 p-2 text-white z-10"
|
||||||
|
>
|
||||||
|
<ChevronRight className="h-6 w-6" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -44,7 +44,7 @@ export const BentoGridItem = React.forwardRef<HTMLDivElement, BentoGridItemProps
|
|||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="flex flex-1 w-full h-full min-h-[6rem] bg-transparent overflow-hidden">
|
<div className="relative w-full h-[65%] min-h-[6rem] bg-transparent overflow-hidden">
|
||||||
{video ? (
|
{video ? (
|
||||||
<video
|
<video
|
||||||
src={video}
|
src={video}
|
||||||
|
Reference in New Issue
Block a user