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 { 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 { AnimatePresence, motion } from "framer-motion";
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import { FadeIn } from "./FadeIn";
|
||||
|
||||
const items = [
|
||||
@@ -53,6 +55,42 @@ const items = [
|
||||
];
|
||||
|
||||
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 (
|
||||
<div>
|
||||
<div className="relative isolate pt-24 pb-12 bg-black text-center w-full lg:px-0 px-4">
|
||||
@@ -64,29 +102,70 @@ export function BentoReviews() {
|
||||
<FadeIn transition={{ duration: 0.8, delay: 0.2 }}>
|
||||
<div className="mx-auto max-w-4xl mt-6 mb-8">
|
||||
<P className="text-center" color="primary">
|
||||
A robust infrastructure layer for autonomous AI agents, our technology stack
|
||||
delivers a secure, efficient, and intuitive platform for deploying and managing AI agents at scale.
|
||||
A robust infrastructure layer for autonomous AI agents, our technology stack
|
||||
delivers a secure, efficient, and intuitive platform for deploying and managing AI agents at scale.
|
||||
</P>
|
||||
</div>
|
||||
</FadeIn>
|
||||
</div>
|
||||
<BentoGrid className="max-w-8xl lg:px-12 px-4pb-12 lg:grid-cols-3">
|
||||
{items.map((item, i) => (
|
||||
<MotionBentoGridItem
|
||||
key={i}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: false, margin: '0px 0px -100px 0px' }}
|
||||
transition={{ duration: 0.8, delay: 0.3 + i * 0.1 }}
|
||||
className={cn(i === 3 || i === 6 ? "md:col-span-2" : "")}
|
||||
rowHeight={i >= 3 ? "h-[22rem]" : ""}
|
||||
title={item.title}
|
||||
subtitle={item.subtitle}
|
||||
description={item.description}
|
||||
video={item.video}
|
||||
/>
|
||||
))}
|
||||
</BentoGrid>
|
||||
|
||||
{/* Desktop Grid */}
|
||||
<div className="hidden lg:block">
|
||||
<BentoGrid className="max-w-8xl lg:px-12 px-4 pb-12 lg:grid-cols-3">
|
||||
{items.map((item, i) => (
|
||||
<MotionBentoGridItem
|
||||
key={i}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: false, margin: '0px 0px -100px 0px' }}
|
||||
transition={{ duration: 0.8, delay: 0.3 + i * 0.1 }}
|
||||
className={cn(i === 3 || i === 6 ? "md:col-span-2" : "")}
|
||||
rowHeight={i >= 3 ? "h-[22rem]" : ""}
|
||||
title={item.title}
|
||||
subtitle={item.subtitle}
|
||||
description={item.description}
|
||||
video={item.video}
|
||||
/>
|
||||
))}
|
||||
</BentoGrid>
|
||||
</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
|
||||
)}
|
||||
>
|
||||
<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
|
||||
src={video}
|
||||
|
Reference in New Issue
Block a user