Files
Wdipl-react/components/CaseStudySlider.tsx

394 lines
14 KiB
TypeScript
Raw Normal View History

// components/CaseStudySlider.tsx
import { motion } from "framer-motion";
import {
Activity,
ArrowUpRight,
Building2,
ChevronLeft, ChevronRight,
FlaskConical,
Globe,
Heart,
Package,
PartyPopper,
ShoppingCart,
Star,
Tractor,
TrendingUp,
Trophy,
Tv,
Users,
Utensils
} from "lucide-react";
import { useEffect, useRef, useState } from "react";
import { useNavigate } from "react-router-dom";
import ambleCase from "../src/images/amble-case.webp";
import amozCase from "../src/images/amozCase.webp";
import dkCase from "../src/images/dkCase.webp";
import farmCase from "../src/images/farmCase.webp";
import gtCase from "../src/images/gt-case.webp";
import leanCase from "../src/images/leanCase.webp";
import niftyCase from "../src/images/niftyCase.webp";
import prospertyCase from "../src/images/prospertyCase.webp";
import ranoutofCase from "../src/images/ranoutof-case.webp";
import RrCase from "../src/images/resturant-reward-case.webp";
2026-02-17 12:08:19 +05:30
// import seezunCase from "../src/images/seezun-case.webp";
import simplitendCase from "../src/images/simplitendCase.webp";
import tcCase from "../src/images/tc-case.webp";
import vib360Case from "../src/images/vib360Case.webp";
import wokaCase from "../src/images/woka-case.webp";
import { ImageWithFallback } from "./figma/ImageWithFallback";
import { Badge } from "./ui/badge";
import { Button } from "./ui/button";
import { Card, CardContent } from "./ui/card";
// Internal data (no need to pass props)
const defaultCaseStudies = [
{
id: 1,
title: "RanOutOf",
image: ranoutofCase,
category: "Lifestyle",
industry: "Consumer",
featured: true,
link: "/projects/ranoutof",
icon: Package
},
2026-02-17 12:08:19 +05:30
// {
// id: 2,
// title: "Seezun",
// image: seezunCase,
// category: "E-commerce",
// industry: "Retail",
// featured: true,
// link: "/projects/seezun",
// icon: ShoppingCart
// },
{
id: 3,
title: "Woka",
image: wokaCase,
category: "Health & Fitness",
industry: "Healthcare",
featured: true,
link: "/projects/woka",
icon: Tv
},
{
id: 6,
title: "Traders Circuit",
image: tcCase,
category: "FinTech",
industry: "Finance",
featured: true,
link: "/projects/traderscircuit",
icon: TrendingUp
},
{
id: 7,
title: "Good Times",
image: gtCase,
category: "Events",
industry: "Entertainment",
featured: true,
link: "/projects/goodtimes",
icon: PartyPopper
},
{
id: 8,
title: "Resturant Reward App",
image: RrCase,
category: "Loyalty & Rewards",
industry: "Hospitality",
featured: false,
link: "/comming-soon",
icon: Utensils
},
{
id: 9,
title: "Amble",
image: ambleCase,
category: "Social",
industry: "Heritage",
featured: false,
link: "/projects/amble",
icon: Users
},
{
id: 10,
title: "Amoz",
image: amozCase,
category: "Lifestyle",
industry: "Consumer",
featured: false,
link: "/projects/amoz",
icon: Heart
},
{
id: 11,
title: "Dorf Ketal",
image: dkCase,
category: "Lifestyle",
industry: "Chemicals",
featured: false,
link: "/comming-soon",
icon: FlaskConical
},
{
id: 12,
title: "VIB360",
image: vib360Case,
category: "Lifestyle",
industry: "Consumer",
featured: false,
link: "/projects/vib360",
icon: Activity
},
{
id: 13,
title: "Nifty 11",
image: niftyCase,
category: "Lifestyle",
industry: "Consumer",
featured: false,
link: "/comming-soon",
icon: Trophy
},
{
id: 14,
title: "Prosperty",
image: prospertyCase,
category: "Lifestyle",
industry: "Real Estate",
featured: false,
link: "/projects/prosperty",
icon: Building2
},
{
id: 15,
title: "Simpletend",
image: simplitendCase,
category: "Health & Fitness",
industry: "Healthcare",
featured: false,
link: "/projects/simpletend",
icon: Activity
},
{
id: 16,
title: "Farm Feeder",
image: farmCase,
category: "AgriTech",
industry: "Agriculture",
featured: false,
link: "/comming-soon",
icon: Tractor
},
{
id: 17,
title: "Lean In World",
image: leanCase,
category: "AgriTech",
industry: "Agriculture",
featured: false,
link: "/comming-soon",
icon: Globe
},
];
interface CaseStudySliderProps {
autoPlay?: boolean;
autoPlayInterval?: number;
}
export const CaseStudySlider = ({
autoPlay = false,
autoPlayInterval = 4000,
}: CaseStudySliderProps) => {
const [currentIndex, setCurrentIndex] = useState(0);
const [isAutoPlaying, setIsAutoPlaying] = useState(autoPlay);
const [visibleSlides, setVisibleSlides] = useState(3);
const sliderRef = useRef<HTMLDivElement>(null);
const containerRef = useRef<HTMLDivElement>(null);
const navigate = useNavigate();
const caseStudies = defaultCaseStudies;
const nextSlide = () => {
setCurrentIndex((prev) => {
const nextIndex = prev + 1;
return nextIndex > caseStudies.length - visibleSlides ? 0 : nextIndex;
});
};
const prevSlide = () => {
setCurrentIndex((prev) => {
const prevIndex = prev - 1;
return prevIndex < 0 ? caseStudies.length - visibleSlides : prevIndex;
});
};
const goToSlide = (index: number) => {
setCurrentIndex(Math.min(index, caseStudies.length - visibleSlides));
};
useEffect(() => {
if (!isAutoPlaying || !autoPlay) return;
const interval = setInterval(nextSlide, autoPlayInterval);
return () => clearInterval(interval);
}, [isAutoPlaying, autoPlay, autoPlayInterval, visibleSlides]);
// Calculate visible slides on resize
useEffect(() => {
const updateVisibleSlides = () => {
if (!containerRef.current) return;
const width = containerRef.current.offsetWidth;
if (width < 640) setVisibleSlides(1);
else if (width < 1024) setVisibleSlides(2);
else setVisibleSlides(3);
};
updateVisibleSlides();
window.addEventListener('resize', updateVisibleSlides);
return () => window.removeEventListener('resize', updateVisibleSlides);
}, []);
const maxIndex = Math.max(0, caseStudies.length - visibleSlides);
// Calculate the actual translation percentage
const getTranslationPercentage = () => {
if (visibleSlides >= caseStudies.length) return 0;
return (currentIndex * (100 / visibleSlides));
};
return (
<section
className="py-20 pt-10"
onMouseEnter={() => setIsAutoPlaying(false)}
onMouseLeave={() => setIsAutoPlaying(autoPlay)}
ref={containerRef}
>
2026-02-17 12:08:19 +05:30
<div className="container px-6 mx-auto lg:px-8">
{/* Header */}
<div className="flex items-center justify-end mb-8">
{caseStudies.length > visibleSlides && (
<div className="flex items-center gap-2">
<Button
variant="outline"
size="icon"
onClick={prevSlide}
disabled={currentIndex === 0}
2026-02-17 12:08:19 +05:30
className="w-10 h-10 rounded-full border-white/20 hover:border-accent/50 disabled:opacity-50"
>
<ChevronLeft className="w-5 h-5" />
</Button>
<Button
variant="outline"
size="icon"
onClick={nextSlide}
disabled={currentIndex >= maxIndex}
2026-02-17 12:08:19 +05:30
className="w-10 h-10 rounded-full border-white/20 hover:border-accent/50 disabled:opacity-50"
>
<ChevronRight className="w-5 h-5" />
</Button>
</div>
)}
</div>
{/* Slider Container */}
<div className="relative overflow-hidden" ref={sliderRef}>
<motion.div
className="flex gap-6"
animate={{
x: `-${getTranslationPercentage()}%`
}}
transition={{
type: "spring",
stiffness: 400,
damping: 40,
duration: 0.6
}}
style={{ willChange: "transform" }}
>
{caseStudies.map((study, index) => {
const IconComponent = study.icon;
return (
<motion.div
key={study.id}
className="flex-shrink-0"
style={{
width: `calc(${100 / visibleSlides}% - 1.5rem)`
}}
initial={{ opacity: 0, scale: 0.9 }}
animate={{ opacity: 1, scale: 1 }}
transition={{
duration: 0.5,
delay: index * 0.1,
ease: "easeOut"
}}
>
<Card
className="bg-card/50 backdrop-blur-md border-white/10 hover:border-accent/30 transition-all duration-500 shadow-lg hover:shadow-2xl hover:shadow-accent/10 rounded-2xl overflow-hidden h-[full] cursor-pointer"
onClick={() => navigate(study.link)}
>
2026-02-17 12:08:19 +05:30
<CardContent className="h-full p-0 CardContentOverride">
{/* Image */}
<div className="relative overflow-hidden">
<ImageWithFallback
src={study.image}
alt={study.title}
2026-02-17 12:08:19 +05:30
className="object-cover w-full h-full transition-transform duration-500 group-hover:scale-110"
/>
<div className="absolute inset-0 bg-gradient-to-t from-black/60 via-transparent to-transparent" />
{/* Category Badge */}
<div className="absolute top-2 left-4">
2026-02-17 12:08:19 +05:30
<Badge className="text-xs text-white border-0 bg-accent/90">
{study.category}
</Badge>
</div>
{/* Featured Badge */}
{study.featured && (
<div className="absolute top-2 right-4">
2026-02-17 12:08:19 +05:30
<div className="flex items-center gap-1 px-2 py-1 text-xs font-medium text-white rounded-full bg-amber-500/90">
<Star className="w-3 h-3 fill-current" />
Featured
</div>
</div>
)}
{/* Icon */}
<div className="absolute bottom-4 left-4">
2026-02-17 12:08:19 +05:30
<div className="flex items-center justify-center w-10 h-10 rounded-full bg-white/20 backdrop-blur-md">
<IconComponent className="w-5 h-5 text-white" />
</div>
</div>
</div>
</CardContent>
</Card>
</motion.div>
);
})}
</motion.div>
</div>
{/* Dots Indicator */}
{caseStudies.length > visibleSlides && (
2026-02-17 12:08:19 +05:30
<div className="flex items-center justify-center gap-2 mt-8">
{Array.from({ length: maxIndex + 1 }).map((_, index) => (
<button
key={index}
onClick={() => goToSlide(index)}
className={`w-2 h-2 rounded-full transition-all duration-300 ${
index === currentIndex
? "bg-accent w-6"
: "bg-white/30 hover:bg-white/50"
}`}
/>
))}
</div>
)}
</div>
</section>
);
};