Retour au catalogue
Portfolio Carousel
Carousel horizontal de projets avec grandes images et overlay d'infos.
portfoliomedium Both Responsive a11y
boldelegantuniversalagencyuniversalcarousel
Theme
"use client";
import React, { useRef } from "react";
import { motion } from "framer-motion";
import { ArrowLeft, ArrowRight } from "lucide-react";
interface ProjectItem {
id: string;
title: string;
category: string;
image?: string;
description?: string;
}
interface PortfolioCarouselProps {
badge?: string;
title?: string;
subtitle?: string;
projects: ProjectItem[];
}
export default function PortfolioCarousel({ badge, title, subtitle, projects }: PortfolioCarouselProps) {
const scrollRef = useRef<HTMLDivElement>(null);
function scroll(dir: "left" | "right") {
if (!scrollRef.current) return;
const amount = scrollRef.current.clientWidth * 0.7;
scrollRef.current.scrollBy({ left: dir === "left" ? -amount : amount, behavior: "smooth" });
}
return (
<section className="py-[var(--section-padding-y,6rem)]" style={{ backgroundColor: "var(--color-background)" }}>
<div className="mx-auto max-w-7xl px-[var(--container-padding-x,1.5rem)]">
<div className="flex flex-col gap-4 sm:flex-row sm:items-end sm:justify-between">
<motion.div initial={{ opacity: 0, y: 20 }} whileInView={{ opacity: 1, y: 0 }} viewport={{ once: true, margin: "-80px" }} transition={{ duration: 0.5 }}>
{badge && <span className="inline-block text-xs font-medium tracking-wider uppercase px-3 py-1 rounded-full border" style={{ color: "var(--color-accent)", borderColor: "var(--color-border)" }}>{badge}</span>}
{title && <h2 className="mt-4 text-3xl font-bold tracking-tight md:text-4xl" style={{ color: "var(--color-foreground)" }}>{title}</h2>}
{subtitle && <p className="mt-2 text-sm" style={{ color: "var(--color-foreground-muted)" }}>{subtitle}</p>}
</motion.div>
<div className="flex gap-2">
<button onClick={() => scroll("left")} className="flex h-10 w-10 items-center justify-center rounded-full border transition-colors" style={{ borderColor: "var(--color-border)", color: "var(--color-foreground)" }} aria-label="Precedent"><ArrowLeft className="h-4 w-4" /></button>
<button onClick={() => scroll("right")} className="flex h-10 w-10 items-center justify-center rounded-full border transition-colors" style={{ borderColor: "var(--color-border)", color: "var(--color-foreground)" }} aria-label="Suivant"><ArrowRight className="h-4 w-4" /></button>
</div>
</div>
<div ref={scrollRef} className="mt-10 flex gap-6 overflow-x-auto pb-4 snap-x snap-mandatory" style={{ scrollbarWidth: "none" }}>
{projects.map((project, i) => (
<motion.div key={project.id} initial={{ opacity: 0, x: 40 }} whileInView={{ opacity: 1, x: 0 }} viewport={{ once: true, margin: "-40px" }} transition={{ delay: i * 0.08, duration: 0.5, ease: [0.16, 1, 0.3, 1] }} className="group flex-shrink-0 w-[min(80vw,500px)] snap-start rounded-[var(--radius-xl,1.5rem)] overflow-hidden border" style={{ backgroundColor: "var(--color-background-card)", borderColor: "var(--color-border)" }}>
<div className="relative aspect-[16/10] overflow-hidden" style={{ backgroundColor: "var(--color-background-alt)", backgroundImage: project.image ? `url(${project.image})` : undefined, backgroundSize: "cover", backgroundPosition: "center" }}>
<div className="absolute inset-0 bg-black/0 group-hover:bg-black/20 transition-colors duration-300" />
</div>
<div className="p-5">
<span className="text-xs font-medium" style={{ color: "var(--color-accent)" }}>{project.category}</span>
<h3 className="mt-1 text-lg font-bold tracking-tight" style={{ color: "var(--color-foreground)" }}>{project.title}</h3>
{project.description && <p className="mt-1.5 text-sm line-clamp-2" style={{ color: "var(--color-foreground-muted)" }}>{project.description}</p>}
</div>
</motion.div>
))}
</div>
</div>
</section>
);
}
Autres variantes portfolio
Portfolio 3D Book
complex · both
elegantelegant
Portfolio Awards Wall
medium · both
elegantbold
Portfolio Case Study Cards
medium · both
corporateelegant
Portfolio Case Study
complex · both
corporatecorporate
Portfolio Client Logos
medium · both
corporateminimal
Portfolio Cursor Follow
complex · both
minimaleditorial