From 8dd658f2066137c26303969cf1ae49a03d9d8436 Mon Sep 17 00:00:00 2001 From: Matiss Jurevics Date: Mon, 15 Dec 2025 19:52:31 +0000 Subject: [PATCH] feat: Add project modal component and integrate it into the product grid. --- src/App.jsx | 5 +- src/components/ProductGrid.jsx | 163 ++++++++++++++++--------------- src/components/ProjectModal.jsx | 165 ++++++++++++++++++++++++++++++++ 3 files changed, 255 insertions(+), 78 deletions(-) create mode 100644 src/components/ProjectModal.jsx diff --git a/src/App.jsx b/src/App.jsx index c82dc63..d6e1124 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -79,11 +79,10 @@ function App() {

{ const gridRef = useRef(null); const titleRef = useRef(null); const itemRefs = useRef([]); + const [selectedProject, setSelectedProject] = useState(null); useEffect(() => { const ctx = gsap.context(() => { @@ -65,86 +67,97 @@ const ProductGrid = () => { }; return ( -
-
-
-

Selected Work

- DESIGN / CODE -
+ <> +
+
+
+

Selected Work

+ DESIGN / CODE +
-
- {products.map((p, i) => ( -
itemRefs.current[i] = el} - className="product-item" - style={{ - background: '#f5f5f5', - height: '450px', - padding: '30px', - display: 'flex', - flexDirection: 'column', - justifyContent: 'space-between', - cursor: 'pointer', - position: 'relative', - overflow: 'hidden' - }} - onMouseEnter={onEnter} - onMouseLeave={onLeave} - > -
- 0{p.id} -
-
+
+ {products.map((p, i) => ( +
itemRefs.current[i] = el} + className="product-item" + style={{ + background: '#f5f5f5', + height: '450px', + padding: '30px', + display: 'flex', + flexDirection: 'column', + justifyContent: 'space-between', + cursor: 'pointer', + position: 'relative', + overflow: 'hidden' + }} + onMouseEnter={onEnter} + onMouseLeave={onLeave} + onClick={() => setSelectedProject(p)} + > +
+ 0{p.id} +
+
- {/* Placeholder for Product Image */} -
- MJ -
+ {/* Placeholder for Product Image */} +
+ MJ +
-
-

{p.name}

-
- {p.desc} - {p.price} +
+

{p.name}

+
+ {p.desc} + {p.price} +
-
- ))} + ))} +
-
-
+
+ + {/* Modal */} + {selectedProject && ( + setSelectedProject(null)} + /> + )} + ); }; diff --git a/src/components/ProjectModal.jsx b/src/components/ProjectModal.jsx new file mode 100644 index 0000000..4636ed1 --- /dev/null +++ b/src/components/ProjectModal.jsx @@ -0,0 +1,165 @@ +import React, { useEffect, useRef } from 'react'; +import gsap from 'gsap'; + +const ProjectModal = ({ project, onClose }) => { + const modalRef = useRef(null); + const overlayRef = useRef(null); + const contentRef = useRef(null); + + useEffect(() => { + const ctx = gsap.context(() => { + // Animation In + gsap.fromTo(overlayRef.current, + { opacity: 0 }, + { opacity: 1, duration: 0.5, ease: "power2.out" } + ); + + gsap.fromTo(contentRef.current, + { y: 100, opacity: 0, scale: 0.95 }, + { y: 0, opacity: 1, scale: 1, duration: 0.5, delay: 0.1, ease: "power3.out" } + ); + }, modalRef); + + // Prevent body scroll + document.body.style.overflow = 'hidden'; + + return () => { + ctx.revert(); + document.body.style.overflow = ''; // Restore scroll + }; + }, []); + + const handleClose = () => { + // Animation Out manually before unmounting + gsap.to(contentRef.current, { y: 50, opacity: 0, duration: 0.3, ease: "power2.in" }); + gsap.to(overlayRef.current, { opacity: 0, duration: 0.3, onComplete: onClose }); + }; + + if (!project) return null; + + return ( +
+ {/* Overlay */} +
+ + {/* Modal Content */} +
+ {/* Close Button */} + + +
+ {/* Left: Image / Visual */} +
+

MJ

+
+ + {/* Right: Info */} +
+
+ + {project.price} {/* Using 'price' for Year based on previous data struct */} + +

+ {project.name} +

+

+ This is a detailed description of the {project.name} project. + It explores the intersection of design and technology, + focusing on user experience and visual impact. +

+ +
+

Role

+

Design, Development

+
+

Tech Stack

+

React, Three.js, GSAP

+
+
+ + +
+
+
+
+ ); +}; + +export default ProjectModal;