1- import { useState , useRef , useCallback } from "react" ;
1+ import { useState } from "react" ;
22import ProjectCard from "./ProjectCard" ;
33
44const Projects = ( ) => {
5- const [ activeIndex , setActiveIndex ] = useState ( 0 ) ;
6- const sliderRef = useRef ( null ) ;
75
8- const projects = [
6+ const projectsData = [
97 {
108 id : 1 ,
119 title : "Portfolio Website" ,
1210 description : "Personal portfolio website built with React + TailwindCSS." ,
1311 image : "projects/portfolio.png" ,
1412 techStack : [ "React" , "TailwindCSS" , "Vite" , "Shadcn/ui" ] ,
1513 status : "completed" ,
14+ category : "Full-Stack Application" ,
1615 link : "https://github.com/JjayFabor/portfolio"
1716 } ,
1817 {
@@ -22,6 +21,7 @@ const Projects = () => {
2221 image : "projects/lettuce-watch.png" ,
2322 techStack : [ "Python" , "Flask" , "Machine Learning (ML)" , "SQLite" , "Arduino" , "HTML" , "CSS" , "JavaScript" ] ,
2423 status : "completed" ,
24+ category : "Full-Stack Application" ,
2525 link : "https://github.com/JjayFabor/LettuceRealTimeMonitoringSystem"
2626 } ,
2727 {
@@ -31,6 +31,7 @@ const Projects = () => {
3131 image : "projects/swiftbidder.png" ,
3232 techStack : [ "Laravel" , "PHP" , "React" , "InertiaJS" , "TailwindCSS" , "MySQL" ] ,
3333 status : "ongoing" ,
34+ category : "Full-Stack Application" ,
3435 link : "https://github.com/JjayFabor/swift-bidder"
3536 } ,
3637 {
@@ -40,75 +41,57 @@ const Projects = () => {
4041 image : "projects/bridgeAI.png" ,
4142 techStack : [ "Python" , "GeminiAPI" , "Flutter" , "Dart" , "Flask" ] ,
4243 status : "ongoing" ,
44+ category : "Mobile Application" ,
4345 link : "https://github.com/JjayFabor/bridgeAI"
4446 } ,
4547 ] ;
4648
47- // Next slide function
48- const nextSlide = useCallback ( ( ) => {
49- setActiveIndex ( ( prevIndex ) => ( prevIndex + 1 ) % projects . length ) ;
50- } , [ projects . length ] ) ;
49+ const [ selectedCategory , setSelectedCategory ] = useState ( "All" ) ;
50+ const categories = [ "All" , ...new Set ( projectsData . map ( ( p ) => p . category ) ) ] ;
5151
52- // Previous slide function
53- const prevSlide = ( ) => {
54- setActiveIndex ( ( prevIndex ) => ( prevIndex - 1 + projects . length ) % projects . length ) ;
55- } ;
52+ const filteredProjects = selectedCategory === "All"
53+ ? projectsData
54+ : projectsData . filter ( ( project ) => project . category === selectedCategory ) ;
5655
57- const goToSlide = ( index ) => setActiveIndex ( index ) ;
5856
5957 return (
6058 < section id = "projects" className = "py-20" >
6159 < div className = "max-w-6xl mx-auto px-4" >
6260 < h2 className = "text-3xl font-bold mb-10 text-center" > Projects</ h2 >
6361
64- < div className = "relative" >
62+ < div className = "flex justify-center gap-2 mb-10 flex-wrap" >
63+ { categories . map ( ( category ) => (
6564 < button
66- onClick = { prevSlide }
67- className = "absolute left-0 top-1/2 transform -translate-y-1/2 z-10 bg-white/80 p-2 rounded-full shadow-md hover:bg-white transition-all"
68- aria-label = "Previous project"
65+ key = { category }
66+ onClick = { ( ) => setSelectedCategory ( category ) }
67+ className = { `px-4 py-2 rounded-md text-sm font-medium transition-colors
68+ ${ selectedCategory === category
69+ ? "bg-primary text-primary-foreground"
70+ : "bg-secondary text-secondary-foreground hover:bg-secondary/80"
71+ } `}
6972 >
70- < svg className = "w-6 h-6" fill = "none" stroke = "currentColor" viewBox = "0 0 24 24" >
71- < path strokeLinecap = "round" strokeLinejoin = "round" strokeWidth = "2" d = "M15 19l-7-7 7-7" />
72- </ svg >
73+ { category }
7374 </ button >
74- { /* Slider */ }
75- < div ref = { sliderRef } className = "overflow-hidden" >
76- < div
77- className = "flex transition-transform duration-500 ease-out"
78- style = { { transform : `translateX(- ${ activeIndex * 100 } %)` } }
79- >
80- { projects . map ( ( project ) => (
75+ ) ) }
76+ </ div >
77+
78+ { filteredProjects . length > 0 ? (
79+ < div className = "overflow-x-auto pb-4" >
80+ < div className = "flex gap-6" >
81+ { filteredProjects . map ( ( project ) => (
8182 < ProjectCard key = { project . id } project = { project } />
8283 ) ) }
8384 </ div >
8485 </ div >
86+ ) : (
87+ < p className = "text-center text-gray-500" > No projects found in this category.</ p >
88+ ) }
8589
86- { /* Next Button */ }
87- < button
88- onClick = { nextSlide }
89- className = "absolute right-0 top-1/2 transform -translate-y-1/2 z-10 bg-white/80 p-2 rounded-full shadow-md hover:bg-white transition-all"
90- aria-label = "Next project"
91- >
92- < svg className = "w-6 h-6" fill = "none" stroke = "currentColor" viewBox = "0 0 24 24" >
93- < path strokeLinecap = "round" strokeLinejoin = "round" strokeWidth = "2" d = "M9 5l7 7-7 7" />
94- </ svg >
95- </ button >
96-
97-
98- { /* Dots */ }
99- < div className = "flex justify-center mt-8 space-x-2" >
100- { projects . map ( ( _ , index ) => (
101- < button
102- key = { index }
103- onClick = { ( ) => goToSlide ( index ) }
104- className = { `w-3 h-3 rounded-full transition-all ${
105- activeIndex === index ? "bg-blue-500 w-6" : "bg-gray-300"
106- } `}
107- aria-label = { `Go to slide ${ index + 1 } ` }
108- />
109- ) ) }
90+ { filteredProjects . length > 0 && (
91+ < div className = "text-center mt-4 text-sm text-gray-500" >
92+ < p > Swipe or scroll to view more projects →</ p >
11093 </ div >
111- </ div >
94+ ) }
11295 </ div >
11396 </ section >
11497 ) ;
0 commit comments