Description
keen.slider.problem.mp4
Here is my code:
import React, { useState, useEffect, useRef } from "react";
import { Button } from "../../ShadcnComponents/button";
import { ArrowRight } from "lucide-react";
import { Projects, Courses } from "./Data";
import { useKeenSlider } from "keen-slider/react";
import "keen-slider/keen-slider.min.css";
import ProjectCard from "./ProjectCard";
import CourseCard from "./CourseCard";
import { limitWords } from "../../../lib/utils";
function Arrow(props) {
const disabled = props.disabled ? " opacity-30 cursor-not-allowed" : "";
return (
<svg
onClick={props.onClick}
className={w-6 h-6 md:w-8 md:h-8 absolute top-1/2 transform -translate-y-1/2 z-10 cursor-pointer fill-primary-foreground ${ props.left ? "-left-1 md:-left-6" : "-right-1 md:-right-6" } ${disabled}
}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
>
{props.left && (
)}
{!props.left && (
)}
);
}
const ProjectCourseCombo = () => {
const [activeTab, setActiveTab] = useState("projects");
const [currentSlide, setCurrentSlide] = useState(0);
const [loaded, setLoaded] = useState(false);
const [sliderInstanceRef, setSliderInstanceRef] = useState(null);
const sliderContainerRef = useRef(null);
// Reset slider when switching tabs
const handleTabChange = (tab) => {
if (tab === activeTab) return;
// First, fade out the content
if (sliderContainerRef.current) {
sliderContainerRef.current.style.opacity = '0';
}
// Change tab after a short delay
setTimeout(() => {
setActiveTab(tab);
setCurrentSlide(0);
// Reset slider and fade back in after content has changed
setTimeout(() => {
if (sliderInstanceRef) {
sliderInstanceRef.update();
sliderInstanceRef.moveToIdx(0);
}
if (sliderContainerRef.current) {
sliderContainerRef.current.style.opacity = '1';
}
}, 50);
}, 150);
};
const [sliderRef, instanceRef] = useKeenSlider({
initial: 0,
loop: true,
slides: {
perView: 4,
spacing: 24,
},
breakpoints: {
"(max-width: 1280px)": {
slides: {
perView: 3,
spacing: 24,
},
},
"(max-width: 1024px)": {
slides: {
perView: 2,
spacing: 16,
},
},
"(max-width: 640px)": {
slides: {
perView: 1,
spacing: 16,
},
},
},
slideChanged(slider) {
setCurrentSlide(slider.track.details.rel);
},
created(slider) {
setLoaded(true);
setSliderInstanceRef(slider);
},
});
const activeData = activeTab === "projects" ? Projects : Courses;
return (
{/* Title */}
Projects and Courses
<section className="p-3 sm:p-4 md:p-6 lg:p-8 bg-primary rounded-3xl mt-4">
<div className="!container mx-auto space-y-6">
{/* Tab Selector */}
<div className="flex justify-center sm:justify-end items-center">
<div className="relative bg-background rounded-full border border-border/20 flex w-fit ">
<button
className={`py-1.5 px-4 sm:py-2 sm:px-6 md:py-3 md:px-8 rounded-full text-base sm:text-lg md:text-xl font-semibold relative z-10 transition-colors ${
activeTab === "projects" ? "text-background" : "text-primary-foreground"
}`}
onClick={() => handleTabChange("projects")}>
Projects
</button>
<button
className={`py-1.5 px-4 sm:py-2 sm:px-6 md:py-3 md:px-8 rounded-full text-base sm:text-lg md:text-xl font-semibold relative z-10 transition-colors ${
activeTab === "courses" ? "text-background" : "text-primary-foreground"
}`}
onClick={() => handleTabChange("courses")}>
Courses
</button>
<div
className={`absolute top-0 h-full bg-primary-foreground rounded-full transition-all duration-300 ${
activeTab === "projects"
? "left-0 w-[100px] sm:w-[120px] md:w-[140px]"
: "left-[100px] sm:left-[120px] md:left-[140px] w-[100px] sm:w-[120px] md:w-[140px]"
}`}></div>
</div>
</div>
{/* Projects/Courses Carousel */}
<div
className="navigation-wrapper relative mb-6 md:mb-8 lg:mb-12 mt-4 sm:mt-6"
ref={sliderContainerRef}
style={{
transition: "opacity 0.15s ease-in-out",
opacity: 1
}}
>
<div ref={sliderRef} className="keen-slider">
{activeData.map((item, index) => {
return activeTab === "projects" ? (
<ProjectCard
key={`project-${item.id}-${index}`}
id={item.id}
title={item.title}
description={item.description}
imageUrl={item.image}
/>
) : (
<CourseCard
key={`course-${item.id}-${index}`}
id={item.id}
title={item.title}
description={item.description}
imageUrl={item.image}
chapters={item.stats?.chapters || 7}
items={item.stats?.sections || 23}
/>
);
})}
</div>
{loaded && instanceRef.current && (
<>
<Arrow
left
onClick={(e) => {
e.stopPropagation();
instanceRef.current?.prev();
}}
disabled={currentSlide === 0}
/>
<Arrow
onClick={(e) => {
e.stopPropagation();
instanceRef.current?.next();
}}
disabled={
currentSlide ===
instanceRef.current.track.details.slides.length - instanceRef.current.options.slides.perView
}
/>
</>
)}
</div>
{/* See All Button */}
<div className="flex justify-center">
<Button variant="cylindrical" size="lg" className=" bg-primary-foreground text-background hover:bg-primary-foreground/80" >
{ activeTab==="projects"? 'All Projects' : 'All Course'}
<ArrowRight size={16} className="sm:size-20" />
</Button>
</div>
</div>
</section>
</div>
);
};
export default ProjectCourseCombo;
child component (keen slider):
import React from "react";
import { useNavigate } from "react-router-dom";
const ProjectCard = ({ id, title, description, imageUrl }) => {
const navigate = useNavigate();
const handleViewDetails = () => {
navigate(/projects/${id}
);
};
return (
{/* Image */}
<img
src={imageUrl}
alt={title}
className="w-full h-full object-cover"
onError={(e) => {
e.target.onerror = null;
e.target.src = "https://images.pexels.com/photos/255377/pexels-photo-255377.jpeg?auto=compress&cs=tinysrgb&w=800";
}}
/>
<h3 className="font-semibold text-sm sm:text-base md:text-lg text-foreground line-clamp-1 overflow-hidden text-ellipsis">
{title}
</h3>
<p className="text-foreground/70 text-xs break-all sm:text-sm md:text-base font-light line-clamp-4 sm:line-clamp-5 overflow-hidden">
{description}
</p>
<p
className="text-primary-foreground text-sm sm:text-base text-right cursor-pointer mt-auto px-3 "
onClick={handleViewDetails}
>
Details
</p>
</div>
</div>
);
};
export default ProjectCard;