v0.7.0 Add filtering feature in projects to filter project cards based on categories
This commit is contained in:
parent
ff04802a61
commit
5a4c05fcba
7 changed files with 130 additions and 7 deletions
35
src/App.jsx
35
src/App.jsx
|
|
@ -114,6 +114,7 @@ const projectsData = [
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
title: "Project 1",
|
title: "Project 1",
|
||||||
|
category: "Web Development",
|
||||||
images: ["/path/to/project1-image1.jpg", "/path/to/project1-image2.jpg"],
|
images: ["/path/to/project1-image1.jpg", "/path/to/project1-image2.jpg"],
|
||||||
description: "A brief description of Project 1.",
|
description: "A brief description of Project 1.",
|
||||||
technologies: ["React", "Node.js", "MongoDB"],
|
technologies: ["React", "Node.js", "MongoDB"],
|
||||||
|
|
@ -121,6 +122,40 @@ const projectsData = [
|
||||||
githubLink: "https://github.com/yourusername/project1",
|
githubLink: "https://github.com/yourusername/project1",
|
||||||
liveLink: "https://project1-demo.com",
|
liveLink: "https://project1-demo.com",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
title: "Project 1",
|
||||||
|
category: "Machine Learning",
|
||||||
|
images: ["/path/to/project1-image1.jpg", "/path/to/project1-image2.jpg"],
|
||||||
|
description: "A brief description of Project 1.",
|
||||||
|
technologies: ["React", "Node.js", "MongoDB"],
|
||||||
|
features: ["User authentication", "Real-time data updates", "Responsive design"],
|
||||||
|
githubLink: "https://github.com/yourusername/project1",
|
||||||
|
liveLink: "https://project1-demo.com",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
title: "Project 1",
|
||||||
|
category: "Embedded Systems",
|
||||||
|
images: ["/path/to/project1-image1.jpg", "/path/to/project1-image2.jpg"],
|
||||||
|
description: "A brief description of Project 1.",
|
||||||
|
technologies: ["React", "Node.js", "MongoDB"],
|
||||||
|
features: ["User authentication", "Real-time data updates", "Responsive design"],
|
||||||
|
githubLink: "https://github.com/yourusername/project1",
|
||||||
|
liveLink: "https://project1-demo.com",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
title: "Project 1",
|
||||||
|
category: "Web Development",
|
||||||
|
images: ["/path/to/project1-image1.jpg", "/path/to/project1-image2.jpg"],
|
||||||
|
description: "A brief description of Project 1.",
|
||||||
|
technologies: ["React", "Node.js", "MongoDB"],
|
||||||
|
features: ["User authentication", "Real-time data updates", "Responsive design"],
|
||||||
|
githubLink: "https://github.com/yourusername/project1",
|
||||||
|
liveLink: "https://project1-demo.com",
|
||||||
|
},
|
||||||
|
|
||||||
// Add more projects...
|
// Add more projects...
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
import React, { useState, useCallback } from "react";
|
import React, { useState, useCallback, useMemo } from "react";
|
||||||
import ProjectCard from "./projectCard/ProjectCard";
|
import ProjectCard from "./projectCard/ProjectCard";
|
||||||
import ProjectModal from "./projectModal/ProjectModal";
|
import ProjectModal from "./projectModal/ProjectModal";
|
||||||
import styles from "./Projects.module.css";
|
import styles from "./Projects.module.css";
|
||||||
|
|
||||||
function Projects({ title, data }) {
|
function Projects({ title, data }) {
|
||||||
const [selectedProject, setSelectedProject] = useState(null);
|
const [selectedProject, setSelectedProject] = useState(null);
|
||||||
|
const [activeFilter, setActiveFilter] = useState("All");
|
||||||
|
const [animatingOut, setAnimatingOut] = useState(false);
|
||||||
|
|
||||||
const openModal = (project) => {
|
const openModal = (project) => {
|
||||||
setSelectedProject(project);
|
setSelectedProject(project);
|
||||||
|
|
@ -14,12 +16,39 @@ function Projects({ title, data }) {
|
||||||
setSelectedProject(null);
|
setSelectedProject(null);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const categories = useMemo(() => {
|
||||||
|
const cats = new Set(data.map((project) => project.category));
|
||||||
|
return ["All", ...Array.from(cats)];
|
||||||
|
}, [data]);
|
||||||
|
|
||||||
|
const filteredProjects = useMemo(() => {
|
||||||
|
if (activeFilter === "All") return data;
|
||||||
|
return data.filter((project) => project.category === activeFilter);
|
||||||
|
}, [data, activeFilter]);
|
||||||
|
|
||||||
|
const handleFilterClick = (category) => {
|
||||||
|
if (category !== activeFilter) {
|
||||||
|
setAnimatingOut(true);
|
||||||
|
setTimeout(() => {
|
||||||
|
setActiveFilter(category);
|
||||||
|
setAnimatingOut(false);
|
||||||
|
}, 300); // This should match the CSS animation duration
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className={styles.projects}>
|
<section className={styles.projects}>
|
||||||
<h2 className={styles.sectionTitle}>{title}</h2>
|
<h2 className={styles.sectionTitle}>{title}</h2>
|
||||||
|
<div className={styles.filterContainer}>
|
||||||
|
{categories.map((category) => (
|
||||||
|
<button key={category} className={`${styles.filterButton} ${activeFilter === category ? styles.active : ""}`} onClick={() => handleFilterClick(category)}>
|
||||||
|
{category}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
<div className={styles.projectGrid}>
|
<div className={styles.projectGrid}>
|
||||||
{data.map((project) => (
|
{filteredProjects.map((project) => (
|
||||||
<ProjectCard key={project.id} project={project} onClick={openModal} />
|
<ProjectCard key={project.id} project={project} onClick={openModal} className={animatingOut ? styles.fadeOut : styles.fadeIn} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
{selectedProject && <ProjectModal project={selectedProject} onClose={closeModal} />}
|
{selectedProject && <ProjectModal project={selectedProject} onClose={closeModal} />}
|
||||||
|
|
|
||||||
|
|
@ -11,12 +11,64 @@
|
||||||
color: #333;
|
color: #333;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.filterContainer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filterButton {
|
||||||
|
background: none;
|
||||||
|
border: 1px solid #333;
|
||||||
|
padding: 8px 16px;
|
||||||
|
margin: 0 8px 8px 0;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filterButton:hover,
|
||||||
|
.filterButton.active {
|
||||||
|
background-color: #333;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
.projectGrid {
|
.projectGrid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
||||||
gap: 2rem;
|
gap: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(20px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeOut {
|
||||||
|
from {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(20px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.fadeIn {
|
||||||
|
animation: fadeIn 0.3s ease forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fadeOut {
|
||||||
|
animation: fadeOut 0.3s ease forwards;
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.projects {
|
.projects {
|
||||||
padding: 30px 5%;
|
padding: 30px 5%;
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,14 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import styles from "./ProjectCard.module.css";
|
import styles from "./ProjectCard.module.css";
|
||||||
|
|
||||||
function ProjectCard({ project, onClick }) {
|
function ProjectCard({ project, onClick, className }) {
|
||||||
return (
|
return (
|
||||||
<div className={styles.card} onClick={() => onClick(project)}>
|
<div className={`${styles.card} ${className}`} onClick={() => onClick(project)}>
|
||||||
<div className={styles.imageSlider}>
|
<div className={styles.imageSlider}>
|
||||||
<img src={project.images[0]} alt={`${project.title} - main image`} className={styles.projectImage} />
|
<img src={project.images[0]} alt={`${project.title} - main image`} className={styles.projectImage} />
|
||||||
</div>
|
</div>
|
||||||
<h3 className={styles.title}>{project.title}</h3>
|
<h3 className={styles.title}>{project.title}</h3>
|
||||||
|
<p className={styles.category}>{project.category}</p>
|
||||||
<p className={styles.description}>{project.description}</p>
|
<p className={styles.description}>{project.description}</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -36,3 +36,9 @@
|
||||||
margin: 0 1rem 1rem;
|
margin: 0 1rem 1rem;
|
||||||
color: #666;
|
color: #666;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.category {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: #666;
|
||||||
|
margin: 0 1rem;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -83,7 +83,7 @@
|
||||||
margin-right: 1rem;
|
margin-right: 1rem;
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
padding: 0.5rem 1rem;
|
padding: 0.5rem 1rem;
|
||||||
background-color: #007bff;
|
background-color: var(--accent-color);
|
||||||
color: white;
|
color: white;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
0.6.2
|
0.7.0
|
||||||
Loading…
Add table
Add a link
Reference in a new issue