From e095c1c6049a008e4370c5fee24d9c3c6b5be7e4 Mon Sep 17 00:00:00 2001 From: JunHam Date: Thu, 10 Jul 2025 22:16:02 +0800 Subject: [PATCH 01/12] Removed chevrons and allowed scrolling for mobile view of catalogueItemView --- .../Catalogue/catalogueItemView.jsx | 314 +++++++++++------- 1 file changed, 196 insertions(+), 118 deletions(-) diff --git a/src/components/Catalogue/catalogueItemView.jsx b/src/components/Catalogue/catalogueItemView.jsx index 20f2e9b..a05aafd 100644 --- a/src/components/Catalogue/catalogueItemView.jsx +++ b/src/components/Catalogue/catalogueItemView.jsx @@ -1,5 +1,5 @@ import { Portal, CloseButton, Image, Box, Text, Icon, useBreakpointValue } from "@chakra-ui/react"; -import { useEffect, useState, useRef } from "react"; +import { useEffect, useState, scrollRef } from "react"; import { motion, AnimatePresence } from "framer-motion"; import { FaChevronLeft, FaChevronRight } from "react-icons/fa"; @@ -13,14 +13,14 @@ const CatalogueItemView = ({ isOpen, onClose, title, items, setDialogTitle, imag const currentIndex = items.findIndex((item) => item.title === title); const prevItem = () => { - if (currentIndex > 0) { + if (!isNavigating && currentIndex > 0) { setIsNavigating(true); setDialogTitle(items[currentIndex - 1].title); } }; const nextItem = () => { - if (currentIndex < items.length - 1) { + if (!isNavigating && currentIndex < items.length - 1) { setIsNavigating(true); setDialogTitle(items[currentIndex + 1].title); } @@ -51,7 +51,7 @@ const CatalogueItemView = ({ isOpen, onClose, title, items, setDialogTitle, imag const chevronPosition = { left: isMobile ? "10px" : "calc(10vw - 60px)", right: isMobile ? "10px" : "calc(10vw - 60px)", - top: isMobile ? "50%" : "50%" + top: isMobile ? "50%" : "50%", }; const chevronSize = isMobile ? "30px" : "40px"; @@ -78,69 +78,81 @@ const CatalogueItemView = ({ isOpen, onClose, title, items, setDialogTitle, imag onClick={onClose} /> - {/* Left Chevron */} - - {currentIndex > 0 && ( - - - - )} - - - {/* Right Chevron */} - - {currentIndex < items.length - 1 && ( - - - - )} - + {!isMobile && ( + + {currentIndex > 0 && ( + + + + )} + + )} + + {!isMobile && ( + + {currentIndex < items.length - 1 && ( + + + + )} + + )} {/* Modal Wrapper */} e.stopPropagation()} > - - {/* Image Section */} - - {title} - + {items.map((item, index) => ( + + {/* Image */} + + {item.title} + - {/* Content Section */} - + + + {item.title} + + {/* Additional content if needed */} + + + ))} + + ) : ( + // --- Desktop Modal View (Single Item) --- + - - - {title} - - {/* Add your content here */} - - + {title} + + + + + {title} + + + + )} @@ -231,4 +309,4 @@ const CatalogueItemView = ({ isOpen, onClose, title, items, setDialogTitle, imag ); }; -export default CatalogueItemView; \ No newline at end of file +export default CatalogueItemView; From 1d046a1fd2d78b371aeaeedcfad9c5fff79033df Mon Sep 17 00:00:00 2001 From: JunHam Date: Thu, 10 Jul 2025 22:21:05 +0800 Subject: [PATCH 02/12] Removed arrow overlay and allow scrolling for mobile view of catalogue --- src/components/Catalogue/section.jsx | 51 ++++++++++++---------------- 1 file changed, 22 insertions(+), 29 deletions(-) diff --git a/src/components/Catalogue/section.jsx b/src/components/Catalogue/section.jsx index e345135..5620b37 100644 --- a/src/components/Catalogue/section.jsx +++ b/src/components/Catalogue/section.jsx @@ -1,4 +1,4 @@ -import { Box, Table, Text, Icon } from "@chakra-ui/react"; +import { Box, Table, Text, Icon, useBreakpointValue } from "@chakra-ui/react"; import { useRef, useState, useEffect } from "react"; import { FaChevronLeft, FaChevronRight } from "react-icons/fa"; import CardItem from "./cardItem.jsx"; @@ -6,7 +6,8 @@ import CatalogueItemView from "./catalogueItemView.jsx"; import ArrowOverlay from "./arrowOverlay.jsx"; const Section = ({ sectionTitle, secLen, selectedTitle, onItemClick }) => { - // Added images array as per your request + const isMobile = useBreakpointValue({ base: true, md: false }); + const images = [ "https://images.unsplash.com/photo-1555041469-a586c61ea9bc?auto=format&fit=crop&w=1770&q=80", "https://images.unsplash.com/photo-1519389950473-47ba0277781c?auto=format&fit=crop&w=1770&q=80", @@ -14,12 +15,11 @@ const Section = ({ sectionTitle, secLen, selectedTitle, onItemClick }) => { "https://images.unsplash.com/photo-1600585154340-be6161a56a0c?auto=format&fit=crop&w=1770&q=80", ]; - // Include imageSrc on each item, cycling images if secLen > images.length const items = Array.from({ length: secLen }).map((_, idx) => ({ id: idx + 1, title: `Photo ${idx + 1}`, description: `Description for photo ${idx + 1}`, - imageSrc: images[idx % images.length], // added this line + imageSrc: images[idx % images.length], })); const scrollRef = useRef(null); @@ -40,9 +40,7 @@ const Section = ({ sectionTitle, secLen, selectedTitle, onItemClick }) => { const handleCardClick = (title) => { setDialogTitle(title); setDialogOpen(true); - if (onItemClick) { - onItemClick(title); - } + if (onItemClick) onItemClick(title); }; useEffect(() => { @@ -68,30 +66,28 @@ const Section = ({ sectionTitle, secLen, selectedTitle, onItemClick }) => { }); }; - // Find current item to pass imageSrc to CatalogueItemView const currentItem = items.find((item) => item.title === dialogTitle); return ( - + {sectionTitle} - scroll("left")} - /> - scroll("right")} - /> + {!isMobile && ( + <> + scroll("left")} + /> + scroll("right")} + /> + + )} @@ -107,12 +103,10 @@ const Section = ({ sectionTitle, secLen, selectedTitle, onItemClick }) => { - handleCardClick(item.title) - } + onClick={() => handleCardClick(item.title)} > { ))} - From 2db90e93147fd82ad55b182473fc9e23775b58b1 Mon Sep 17 00:00:00 2001 From: JunHam Date: Thu, 10 Jul 2025 22:24:58 +0800 Subject: [PATCH 03/12] Removed arrow overlay for MMSection if isMobile --- src/components/Catalogue/mmSection.jsx | 33 ++++++++++++-------------- 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/src/components/Catalogue/mmSection.jsx b/src/components/Catalogue/mmSection.jsx index cb6e3a5..c61fefe 100644 --- a/src/components/Catalogue/mmSection.jsx +++ b/src/components/Catalogue/mmSection.jsx @@ -1,9 +1,11 @@ -import { Box, Table, Text, Icon } from "@chakra-ui/react"; +import { Box, Table, Text, useBreakpointValue } from "@chakra-ui/react"; import { useRef, useState, useEffect } from "react"; import CardItem from "./cardItem.jsx"; import ArrowOverlay from "./arrowOverlay.jsx"; const MMSection = ({ onItemClick, selectedTitle }) => { + const isMobile = useBreakpointValue({ base: true, md: false }); + const items = Array.from({ length: 15 }).map((_, idx) => ({ id: idx + 1, title: `Meeting ${idx + 1}`, @@ -18,15 +20,7 @@ const MMSection = ({ onItemClick, selectedTitle }) => { const node = scrollRef.current; if (!node) return; setAtStart(node.scrollLeft === 0); - setAtEnd( - Math.ceil(node.scrollLeft + node.clientWidth) >= node.scrollWidth - ); - }; - - const handleClick = (title) => { - if (onItemClick) { - onItemClick(title); - } + setAtEnd(Math.ceil(node.scrollLeft + node.clientWidth) >= node.scrollWidth); }; useEffect(() => { @@ -52,19 +46,22 @@ const MMSection = ({ onItemClick, selectedTitle }) => { }); }; + const handleClick = (title) => { + if (onItemClick) onItemClick(title); + }; + return ( - + Meeting Minutes - scroll("left")} /> - scroll("right")} /> + {!isMobile && ( + <> + scroll("left")} /> + scroll("right")} /> + + )} From 6eff800152206cfab6f7aa923fd255ad986f28cd Mon Sep 17 00:00:00 2001 From: JunHam Date: Thu, 10 Jul 2025 22:37:02 +0800 Subject: [PATCH 04/12] Fixed scrollings for mobile view --- src/components/Catalogue/cardItem.jsx | 96 +++++++------- .../Catalogue/catalogueItemView.jsx | 123 ++++++------------ src/components/Catalogue/mmSection.jsx | 2 +- src/components/Catalogue/section.jsx | 2 +- 4 files changed, 90 insertions(+), 133 deletions(-) diff --git a/src/components/Catalogue/cardItem.jsx b/src/components/Catalogue/cardItem.jsx index 71890d8..ef1fcf3 100644 --- a/src/components/Catalogue/cardItem.jsx +++ b/src/components/Catalogue/cardItem.jsx @@ -1,5 +1,4 @@ -// CardItem.jsx -import { Box, Card, Image, Text } from "@chakra-ui/react"; +import { Box, Card, Image, Text, useBreakpointValue } from "@chakra-ui/react"; import React, { useState, useRef, useEffect } from "react"; import { motion, AnimatePresence } from "framer-motion"; @@ -13,6 +12,8 @@ const images = [ ]; const CardItem = ({ itemTitle, itemDescription, selectedTitle, index }) => { + const isMobile = useBreakpointValue({ base: true, md: false }); + const [isHovered, setIsHovered] = useState(false); const [isSelected, setIsSelected] = useState(selectedTitle === itemTitle); const cardRef = useRef(null); @@ -47,6 +48,7 @@ const CardItem = ({ itemTitle, itemDescription, selectedTitle, index }) => { }; const handleMouseEnter = () => { + if (isMobile) return; hoverTimeoutRef.current = setTimeout(() => { updateHoverPosition(); setIsHovered(true); @@ -59,7 +61,7 @@ const CardItem = ({ itemTitle, itemDescription, selectedTitle, index }) => { }; useEffect(() => { - if (isHovered) { + if (isHovered && !isMobile) { let node = cardRef.current; while (node) { const style = window.getComputedStyle(node); @@ -78,24 +80,18 @@ const CardItem = ({ itemTitle, itemDescription, selectedTitle, index }) => { window.addEventListener("scroll", updateHoverPosition, true); window.addEventListener("resize", updateHoverPosition); if (scrollContainerRef.current) { - scrollContainerRef.current.addEventListener( - "scroll", - updateHoverPosition - ); + scrollContainerRef.current.addEventListener("scroll", updateHoverPosition); } return () => { window.removeEventListener("scroll", updateHoverPosition, true); window.removeEventListener("resize", updateHoverPosition); if (scrollContainerRef.current) { - scrollContainerRef.current.removeEventListener( - "scroll", - updateHoverPosition - ); + scrollContainerRef.current.removeEventListener("scroll", updateHoverPosition); } }; } - }, [isHovered]); + }, [isHovered, isMobile]); const imageSrc = images[index % images.length]; @@ -126,43 +122,45 @@ const CardItem = ({ itemTitle, itemDescription, selectedTitle, index }) => { - - {isHovered && ( - setIsHovered(true)} - borderRadius="md" - border={isSelected ? "1px solid" : "none"} - borderColor={isSelected ? "blue.600" : "transparent"} - boxShadow={ - isSelected - ? "0 0 15px 2px rgba(66,153,225,0.6)" - : "2xl" - } - > - - {itemTitle} - - {itemTitle} - {itemDescription} - - - - )} - + {!isMobile && ( + + {isHovered && ( + setIsHovered(true)} + borderRadius="md" + border={isSelected ? "1px solid" : "none"} + borderColor={isSelected ? "blue.600" : "transparent"} + boxShadow={ + isSelected + ? "0 0 15px 2px rgba(66,153,225,0.6)" + : "2xl" + } + > + + {itemTitle} + + {itemTitle} + {itemDescription} + + + + )} + + )} ); }; diff --git a/src/components/Catalogue/catalogueItemView.jsx b/src/components/Catalogue/catalogueItemView.jsx index a05aafd..a724405 100644 --- a/src/components/Catalogue/catalogueItemView.jsx +++ b/src/components/Catalogue/catalogueItemView.jsx @@ -1,5 +1,5 @@ import { Portal, CloseButton, Image, Box, Text, Icon, useBreakpointValue } from "@chakra-ui/react"; -import { useEffect, useState, scrollRef } from "react"; +import { useEffect, useState, useRef } from "react"; import { motion, AnimatePresence } from "framer-motion"; import { FaChevronLeft, FaChevronRight } from "react-icons/fa"; @@ -9,6 +9,8 @@ const MotionChevron = motion.create(Box); const CatalogueItemView = ({ isOpen, onClose, title, items, setDialogTitle, imageSrc }) => { const [isNavigating, setIsNavigating] = useState(false); const isMobile = useBreakpointValue({ base: true, md: false }); + const [hasRendered, setHasRendered] = useState(false); + const scrollRef = useRef(null); const currentIndex = items.findIndex((item) => item.title === title); @@ -26,7 +28,6 @@ const CatalogueItemView = ({ isOpen, onClose, title, items, setDialogTitle, imag } }; - // Lock body scroll when modal is open useEffect(() => { document.body.style.overflow = isOpen ? "hidden" : ""; return () => { @@ -34,7 +35,6 @@ const CatalogueItemView = ({ isOpen, onClose, title, items, setDialogTitle, imag }; }, [isOpen]); - // Reset isNavigating after item change animation useEffect(() => { if (isNavigating) { const timeout = setTimeout(() => setIsNavigating(false), 250); @@ -42,7 +42,23 @@ const CatalogueItemView = ({ isOpen, onClose, title, items, setDialogTitle, imag } }, [title]); - // Responsive values + // Reset flag when modal closes + useEffect(() => { + if (!isOpen) { + setHasRendered(false); + } + }, [isOpen]); + + // Scroll to current index once rendered (mobile) + useEffect(() => { + if (isMobile && isOpen && hasRendered && scrollRef.current) { + scrollRef.current.scrollTo({ + left: currentIndex * window.innerWidth, + behavior: "instant", + }); + } + }, [isMobile, isOpen, hasRendered, currentIndex]); + const modalSize = { width: isMobile ? "100vw" : "80vw", height: isMobile ? "100vh" : "80vh", @@ -51,7 +67,7 @@ const CatalogueItemView = ({ isOpen, onClose, title, items, setDialogTitle, imag const chevronPosition = { left: isMobile ? "10px" : "calc(10vw - 60px)", right: isMobile ? "10px" : "calc(10vw - 60px)", - top: isMobile ? "50%" : "50%", + top: "50%", }; const chevronSize = isMobile ? "30px" : "40px"; @@ -61,7 +77,6 @@ const CatalogueItemView = ({ isOpen, onClose, title, items, setDialogTitle, imag {isOpen && ( <> - {/* Overlay */} {!isMobile && ( - + <> {currentIndex > 0 && ( - + )} - - )} - {!isMobile && ( - {currentIndex < items.length - 1 && ( - + )} - + )} - {/* Modal Wrapper */} {isMobile ? ( - // --- Mobile Scroll View --- - {items.map((item, index) => ( + {items.map((item) => ( - {/* Image */} - + {item.title} - - {/* Content */} - - + + {item.title} - {/* Additional content if needed */} ))} + {/* Trigger to set flag when layout is ready */} + {!hasRendered && ( + setHasRendered(true)} /> + )} ) : ( - // --- Desktop Modal View (Single Item) --- - + {title} - - - - {title} - + + + {title} )} @@ -309,4 +268,4 @@ const CatalogueItemView = ({ isOpen, onClose, title, items, setDialogTitle, imag ); }; -export default CatalogueItemView; +export default CatalogueItemView; \ No newline at end of file diff --git a/src/components/Catalogue/mmSection.jsx b/src/components/Catalogue/mmSection.jsx index c61fefe..d7a40d4 100644 --- a/src/components/Catalogue/mmSection.jsx +++ b/src/components/Catalogue/mmSection.jsx @@ -67,7 +67,7 @@ const MMSection = ({ onItemClick, selectedTitle }) => { - + {items.map((item, index) => ( { - + {items.map((item, index) => ( Date: Thu, 10 Jul 2025 23:08:09 +0800 Subject: [PATCH 05/12] Made catalogueItemView overlay scrollable using arrow keys --- src/components/Catalogue/catalogueItemView.jsx | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/components/Catalogue/catalogueItemView.jsx b/src/components/Catalogue/catalogueItemView.jsx index a724405..b0820ea 100644 --- a/src/components/Catalogue/catalogueItemView.jsx +++ b/src/components/Catalogue/catalogueItemView.jsx @@ -59,6 +59,24 @@ const CatalogueItemView = ({ isOpen, onClose, title, items, setDialogTitle, imag } }, [isMobile, isOpen, hasRendered, currentIndex]); + useEffect(() => { + const handleKeyDown = (e) => { + if (!isOpen || isNavigating) return; + + if (e.key === "ArrowLeft" && currentIndex > 0) { + prevItem(); + } else if (e.key === "ArrowRight" && currentIndex < items.length - 1) { + nextItem(); + } + }; + + window.addEventListener("keydown", handleKeyDown); + return () => { + window.removeEventListener("keydown", handleKeyDown); + }; +}, [isOpen, isNavigating, currentIndex, items]); + + const modalSize = { width: isMobile ? "100vw" : "80vw", height: isMobile ? "100vh" : "80vh", From 21eda3635e5e9e880390567464e495112e85a2c2 Mon Sep 17 00:00:00 2001 From: JunHam Date: Thu, 10 Jul 2025 23:15:04 +0800 Subject: [PATCH 06/12] Fixed background scrolling when catalogueItemView is scrolling using arrow key --- .../Catalogue/catalogueItemView.jsx | 138 ++++++++++++++---- 1 file changed, 113 insertions(+), 25 deletions(-) diff --git a/src/components/Catalogue/catalogueItemView.jsx b/src/components/Catalogue/catalogueItemView.jsx index b0820ea..318145a 100644 --- a/src/components/Catalogue/catalogueItemView.jsx +++ b/src/components/Catalogue/catalogueItemView.jsx @@ -1,4 +1,12 @@ -import { Portal, CloseButton, Image, Box, Text, Icon, useBreakpointValue } from "@chakra-ui/react"; +import { + Portal, + CloseButton, + Image, + Box, + Text, + Icon, + useBreakpointValue, +} from "@chakra-ui/react"; import { useEffect, useState, useRef } from "react"; import { motion, AnimatePresence } from "framer-motion"; import { FaChevronLeft, FaChevronRight } from "react-icons/fa"; @@ -6,11 +14,19 @@ import { FaChevronLeft, FaChevronRight } from "react-icons/fa"; const MotionBox = motion.create(Box); const MotionChevron = motion.create(Box); -const CatalogueItemView = ({ isOpen, onClose, title, items, setDialogTitle, imageSrc }) => { +const CatalogueItemView = ({ + isOpen, + onClose, + title, + items, + setDialogTitle, + imageSrc, +}) => { const [isNavigating, setIsNavigating] = useState(false); const isMobile = useBreakpointValue({ base: true, md: false }); const [hasRendered, setHasRendered] = useState(false); const scrollRef = useRef(null); + const modalRef = useRef(null); const currentIndex = items.findIndex((item) => item.title === title); @@ -60,23 +76,43 @@ const CatalogueItemView = ({ isOpen, onClose, title, items, setDialogTitle, imag }, [isMobile, isOpen, hasRendered, currentIndex]); useEffect(() => { + const handleKeyDown = (e) => { + if (!isOpen || isNavigating) return; + + if (e.key === "ArrowLeft" && currentIndex > 0) { + prevItem(); + } else if ( + e.key === "ArrowRight" && + currentIndex < items.length - 1 + ) { + nextItem(); + } + }; + + window.addEventListener("keydown", handleKeyDown); + return () => { + window.removeEventListener("keydown", handleKeyDown); + }; + }, [isOpen, isNavigating, currentIndex, items]); + + useEffect(() => { + if (isOpen && modalRef.current) { + modalRef.current.focus(); + } + }, [isOpen]); + const handleKeyDown = (e) => { - if (!isOpen || isNavigating) return; + if (isNavigating) return; if (e.key === "ArrowLeft" && currentIndex > 0) { + e.preventDefault(); prevItem(); } else if (e.key === "ArrowRight" && currentIndex < items.length - 1) { + e.preventDefault(); nextItem(); } }; - window.addEventListener("keydown", handleKeyDown); - return () => { - window.removeEventListener("keydown", handleKeyDown); - }; -}, [isOpen, isNavigating, currentIndex, items]); - - const modalSize = { width: isMobile ? "100vw" : "80vw", height: isMobile ? "100vh" : "80vh", @@ -134,11 +170,16 @@ const CatalogueItemView = ({ isOpen, onClose, title, items, setDialogTitle, imag boxShadow="md" cursor="pointer" _hover={{ - transform: "translateY(-50%) scale(1.2)", + transform: + "translateY(-50%) scale(1.2)", }} onClick={prevItem} > - + )} @@ -163,17 +204,24 @@ const CatalogueItemView = ({ isOpen, onClose, title, items, setDialogTitle, imag boxShadow="md" cursor="pointer" _hover={{ - transform: "translateY(-50%) scale(1.2)", + transform: + "translateY(-50%) scale(1.2)", }} onClick={nextItem} > - + )} )} - + {item.title} - - + + {item.title} ))} - {/* Trigger to set flag when layout is ready */} {!hasRendered && ( - setHasRendered(true)} /> + + setHasRendered(true) + } + /> )} ) : ( @@ -261,7 +328,12 @@ const CatalogueItemView = ({ isOpen, onClose, title, items, setDialogTitle, imag top={0} left={0} > - + {title} - - - {title} + + + + {title} + )} @@ -286,4 +374,4 @@ const CatalogueItemView = ({ isOpen, onClose, title, items, setDialogTitle, imag ); }; -export default CatalogueItemView; \ No newline at end of file +export default CatalogueItemView; From 9f6018ee97af8240b8e33b343ebefce495391214 Mon Sep 17 00:00:00 2001 From: JunHam Date: Fri, 11 Jul 2025 23:44:51 +0800 Subject: [PATCH 07/12] Added skeleton for cardItem --- src/components/Catalogue/cardItem.jsx | 66 ++++++++++++++++++++++----- 1 file changed, 54 insertions(+), 12 deletions(-) diff --git a/src/components/Catalogue/cardItem.jsx b/src/components/Catalogue/cardItem.jsx index ef1fcf3..3d97d73 100644 --- a/src/components/Catalogue/cardItem.jsx +++ b/src/components/Catalogue/cardItem.jsx @@ -1,5 +1,5 @@ -import { Box, Card, Image, Text, useBreakpointValue } from "@chakra-ui/react"; -import React, { useState, useRef, useEffect } from "react"; +import { Box, Card, Image, Text, useBreakpointValue, Skeleton } from "@chakra-ui/react"; +import { useState, useRef, useEffect } from "react"; import { motion, AnimatePresence } from "framer-motion"; const MotionBox = motion.create(Box); @@ -16,6 +16,8 @@ const CardItem = ({ itemTitle, itemDescription, selectedTitle, index }) => { const [isHovered, setIsHovered] = useState(false); const [isSelected, setIsSelected] = useState(selectedTitle === itemTitle); + const [isLoading, setIsLoading] = useState(true); + const cardRef = useRef(null); const [hoverPos, setHoverPos] = useState({ top: 0, left: 0 }); const scrollContainerRef = useRef(null); @@ -25,6 +27,13 @@ const CardItem = ({ itemTitle, itemDescription, selectedTitle, index }) => { setIsSelected(selectedTitle === itemTitle); }, [selectedTitle, itemTitle]); + const imageSrc = images[index % images.length]; + + // Reset loading when image changes + useEffect(() => { + setIsLoading(true); + }, [imageSrc]); + const updateHoverPosition = () => { const rect = cardRef.current?.getBoundingClientRect(); if (rect) { @@ -80,21 +89,25 @@ const CardItem = ({ itemTitle, itemDescription, selectedTitle, index }) => { window.addEventListener("scroll", updateHoverPosition, true); window.addEventListener("resize", updateHoverPosition); if (scrollContainerRef.current) { - scrollContainerRef.current.addEventListener("scroll", updateHoverPosition); + scrollContainerRef.current.addEventListener( + "scroll", + updateHoverPosition + ); } return () => { window.removeEventListener("scroll", updateHoverPosition, true); window.removeEventListener("resize", updateHoverPosition); if (scrollContainerRef.current) { - scrollContainerRef.current.removeEventListener("scroll", updateHoverPosition); + scrollContainerRef.current.removeEventListener( + "scroll", + updateHoverPosition + ); } }; } }, [isHovered, isMobile]); - const imageSrc = images[index % images.length]; - return ( <> { boxShadow={isSelected ? "lg" : "md"} transition="all 0.3s ease-in-out" > - {itemTitle} + + {itemTitle} setIsLoading(false)} + /> + - {itemTitle} + + {itemTitle} + @@ -143,7 +165,9 @@ const CardItem = ({ itemTitle, itemDescription, selectedTitle, index }) => { onMouseEnter={() => setIsHovered(true)} borderRadius="md" border={isSelected ? "1px solid" : "none"} - borderColor={isSelected ? "blue.600" : "transparent"} + borderColor={ + isSelected ? "blue.600" : "transparent" + } boxShadow={ isSelected ? "0 0 15px 2px rgba(66,153,225,0.6)" @@ -151,10 +175,28 @@ const CardItem = ({ itemTitle, itemDescription, selectedTitle, index }) => { } > - {itemTitle} + + {itemTitle} setIsLoading(false)} + /> + - {itemTitle} - {itemDescription} + + {itemTitle} + + + {itemDescription} + From a8acc0d3e61fd4936ff64144aa67a4bfcf4ac853 Mon Sep 17 00:00:00 2001 From: JunHam Date: Sat, 12 Jul 2025 00:03:15 +0800 Subject: [PATCH 08/12] Added swipe animation for catalogueItemView --- .../Catalogue/catalogueItemView.jsx | 142 ++++++++++++------ 1 file changed, 100 insertions(+), 42 deletions(-) diff --git a/src/components/Catalogue/catalogueItemView.jsx b/src/components/Catalogue/catalogueItemView.jsx index 318145a..38a3e9e 100644 --- a/src/components/Catalogue/catalogueItemView.jsx +++ b/src/components/Catalogue/catalogueItemView.jsx @@ -11,8 +11,8 @@ import { useEffect, useState, useRef } from "react"; import { motion, AnimatePresence } from "framer-motion"; import { FaChevronLeft, FaChevronRight } from "react-icons/fa"; -const MotionBox = motion.create(Box); -const MotionChevron = motion.create(Box); +const MotionBox = motion(Box); +const MotionChevron = motion(Box); const CatalogueItemView = ({ isOpen, @@ -23,6 +23,8 @@ const CatalogueItemView = ({ imageSrc, }) => { const [isNavigating, setIsNavigating] = useState(false); + const [direction, setDirection] = useState(0); // Track navigation direction + const [isInitialRender, setIsInitialRender] = useState(true); // Track initial render const isMobile = useBreakpointValue({ base: true, md: false }); const [hasRendered, setHasRendered] = useState(false); const scrollRef = useRef(null); @@ -32,6 +34,7 @@ const CatalogueItemView = ({ const prevItem = () => { if (!isNavigating && currentIndex > 0) { + setDirection(-1); // Set direction for animation setIsNavigating(true); setDialogTitle(items[currentIndex - 1].title); } @@ -39,6 +42,7 @@ const CatalogueItemView = ({ const nextItem = () => { if (!isNavigating && currentIndex < items.length - 1) { + setDirection(1); // Set direction for animation setIsNavigating(true); setDialogTitle(items[currentIndex + 1].title); } @@ -58,10 +62,12 @@ const CatalogueItemView = ({ } }, [title]); - // Reset flag when modal closes + // Reset flags when modal closes useEffect(() => { if (!isOpen) { setHasRendered(false); + setDirection(0); + setIsInitialRender(true); // Reset initial render flag } }, [isOpen]); @@ -75,6 +81,7 @@ const CatalogueItemView = ({ } }, [isMobile, isOpen, hasRendered, currentIndex]); + // Keyboard navigation useEffect(() => { const handleKeyDown = (e) => { if (!isOpen || isNavigating) return; @@ -126,6 +133,22 @@ const CatalogueItemView = ({ const chevronSize = isMobile ? "30px" : "40px"; + // Animation variants for desktop content + const desktopVariants = { + enter: (direction) => ({ + x: direction > 0 ? "100%" : "-100%", + opacity: 0, + }), + center: { + x: 0, + opacity: 1, + }, + exit: (direction) => ({ + x: direction > 0 ? "-100%" : "100%", + opacity: 0, + }), + }; + return ( @@ -318,51 +341,86 @@ const CatalogueItemView = ({ )} ) : ( - - - {title} - - - - { + // After first animation, mark as non-initial + if (isInitialRender) { + setIsInitialRender( + false + ); + } + }} > - {title} - - - + + {title} + + + + + {title} + + + + + )} @@ -374,4 +432,4 @@ const CatalogueItemView = ({ ); }; -export default CatalogueItemView; +export default CatalogueItemView; \ No newline at end of file From dd05ec14a17f408a76cc7ab3e6d56bbda3cf2158 Mon Sep 17 00:00:00 2001 From: JunHam Date: Sat, 12 Jul 2025 00:21:13 +0800 Subject: [PATCH 09/12] Tidy up code for catalogueItemView --- .../Catalogue/catalogueItemView.jsx | 187 +++++------------- 1 file changed, 44 insertions(+), 143 deletions(-) diff --git a/src/components/Catalogue/catalogueItemView.jsx b/src/components/Catalogue/catalogueItemView.jsx index 38a3e9e..159391c 100644 --- a/src/components/Catalogue/catalogueItemView.jsx +++ b/src/components/Catalogue/catalogueItemView.jsx @@ -1,12 +1,4 @@ -import { - Portal, - CloseButton, - Image, - Box, - Text, - Icon, - useBreakpointValue, -} from "@chakra-ui/react"; +import { Portal, CloseButton, Image, Box, Text, Icon, useBreakpointValue } from "@chakra-ui/react"; import { useEffect, useState, useRef } from "react"; import { motion, AnimatePresence } from "framer-motion"; import { FaChevronLeft, FaChevronRight } from "react-icons/fa"; @@ -14,19 +6,12 @@ import { FaChevronLeft, FaChevronRight } from "react-icons/fa"; const MotionBox = motion(Box); const MotionChevron = motion(Box); -const CatalogueItemView = ({ - isOpen, - onClose, - title, - items, - setDialogTitle, - imageSrc, -}) => { +const CatalogueItemView = ({ isOpen, onClose, title, items, setDialogTitle, imageSrc }) => { const [isNavigating, setIsNavigating] = useState(false); - const [direction, setDirection] = useState(0); // Track navigation direction - const [isInitialRender, setIsInitialRender] = useState(true); // Track initial render - const isMobile = useBreakpointValue({ base: true, md: false }); + const [direction, setDirection] = useState(0); + const [isInitialRender, setIsInitialRender] = useState(true); const [hasRendered, setHasRendered] = useState(false); + const isMobile = useBreakpointValue({ base: true, md: false }); const scrollRef = useRef(null); const modalRef = useRef(null); @@ -34,7 +19,7 @@ const CatalogueItemView = ({ const prevItem = () => { if (!isNavigating && currentIndex > 0) { - setDirection(-1); // Set direction for animation + setDirection(-1); setIsNavigating(true); setDialogTitle(items[currentIndex - 1].title); } @@ -42,12 +27,13 @@ const CatalogueItemView = ({ const nextItem = () => { if (!isNavigating && currentIndex < items.length - 1) { - setDirection(1); // Set direction for animation + setDirection(1); setIsNavigating(true); setDialogTitle(items[currentIndex + 1].title); } }; + // Prevent background scroll when modal is open useEffect(() => { document.body.style.overflow = isOpen ? "hidden" : ""; return () => { @@ -55,6 +41,7 @@ const CatalogueItemView = ({ }; }, [isOpen]); + // Allow new navigation after animation ends useEffect(() => { if (isNavigating) { const timeout = setTimeout(() => setIsNavigating(false), 250); @@ -67,11 +54,11 @@ const CatalogueItemView = ({ if (!isOpen) { setHasRendered(false); setDirection(0); - setIsInitialRender(true); // Reset initial render flag + setIsInitialRender(true); } }, [isOpen]); - // Scroll to current index once rendered (mobile) + // Scroll to correct position on mobile when rendered useEffect(() => { if (isMobile && isOpen && hasRendered && scrollRef.current) { scrollRef.current.scrollTo({ @@ -85,32 +72,21 @@ const CatalogueItemView = ({ useEffect(() => { const handleKeyDown = (e) => { if (!isOpen || isNavigating) return; - - if (e.key === "ArrowLeft" && currentIndex > 0) { - prevItem(); - } else if ( - e.key === "ArrowRight" && - currentIndex < items.length - 1 - ) { - nextItem(); - } + if (e.key === "ArrowLeft" && currentIndex > 0) prevItem(); + else if (e.key === "ArrowRight" && currentIndex < items.length - 1) nextItem(); }; - window.addEventListener("keydown", handleKeyDown); - return () => { - window.removeEventListener("keydown", handleKeyDown); - }; + return () => window.removeEventListener("keydown", handleKeyDown); }, [isOpen, isNavigating, currentIndex, items]); + // Focus modal when open useEffect(() => { - if (isOpen && modalRef.current) { - modalRef.current.focus(); - } + if (isOpen && modalRef.current) modalRef.current.focus(); }, [isOpen]); + // Local keydown handler const handleKeyDown = (e) => { if (isNavigating) return; - if (e.key === "ArrowLeft" && currentIndex > 0) { e.preventDefault(); prevItem(); @@ -133,20 +109,10 @@ const CatalogueItemView = ({ const chevronSize = isMobile ? "30px" : "40px"; - // Animation variants for desktop content const desktopVariants = { - enter: (direction) => ({ - x: direction > 0 ? "100%" : "-100%", - opacity: 0, - }), - center: { - x: 0, - opacity: 1, - }, - exit: (direction) => ({ - x: direction > 0 ? "-100%" : "100%", - opacity: 0, - }), + enter: (direction) => ({ x: direction > 0 ? "100%" : "-100%", opacity: 0 }), + center: { x: 0, opacity: 1 }, + exit: (direction) => ({ x: direction > 0 ? "-100%" : "100%", opacity: 0 }), }; return ( @@ -154,6 +120,7 @@ const CatalogueItemView = ({ {isOpen && ( <> + {/* Overlay */} + {/* Desktop chevrons */} {!isMobile && ( <> {currentIndex > 0 && ( @@ -192,17 +160,10 @@ const CatalogueItemView = ({ justifyContent="center" boxShadow="md" cursor="pointer" - _hover={{ - transform: - "translateY(-50%) scale(1.2)", - }} + _hover={{ transform: "translateY(-50%) scale(1.2)" }} onClick={prevItem} > - + )} @@ -226,22 +187,16 @@ const CatalogueItemView = ({ justifyContent="center" boxShadow="md" cursor="pointer" - _hover={{ - transform: - "translateY(-50%) scale(1.2)", - }} + _hover={{ transform: "translateY(-50%) scale(1.2)" }} onClick={nextItem} > - + )} )} + {/* Modal content */} {isMobile ? ( + // Mobile: horizontal swipe scroll view {items.map((item) => ( @@ -295,11 +248,7 @@ const CatalogueItemView = ({ display="flex" flexDirection="column" > - + {item.title} - - + + {item.title} ))} {!hasRendered && ( - - setHasRendered(true) - } - /> + setHasRendered(true)} /> )} ) : ( - // Desktop content with sliding animations - - + // Desktop: sliding animation + + { - // After first animation, mark as non-initial - if (isInitialRender) { - setIsInitialRender( - false - ); - } + if (isInitialRender) setIsInitialRender(false); }} > - + {title} - - + + {title} @@ -432,4 +333,4 @@ const CatalogueItemView = ({ ); }; -export default CatalogueItemView; \ No newline at end of file +export default CatalogueItemView; From d6ec960cae206744b505becc22b0d4291f323d8c Mon Sep 17 00:00:00 2001 From: JunHam Date: Sat, 12 Jul 2025 00:23:24 +0800 Subject: [PATCH 10/12] Updated motion() to use motion.create() instead --- src/components/Catalogue/catalogueItemView.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/Catalogue/catalogueItemView.jsx b/src/components/Catalogue/catalogueItemView.jsx index 159391c..399affa 100644 --- a/src/components/Catalogue/catalogueItemView.jsx +++ b/src/components/Catalogue/catalogueItemView.jsx @@ -3,8 +3,8 @@ import { useEffect, useState, useRef } from "react"; import { motion, AnimatePresence } from "framer-motion"; import { FaChevronLeft, FaChevronRight } from "react-icons/fa"; -const MotionBox = motion(Box); -const MotionChevron = motion(Box); +const MotionBox = motion.create(Box); +const MotionChevron = motion.create(Box); const CatalogueItemView = ({ isOpen, onClose, title, items, setDialogTitle, imageSrc }) => { const [isNavigating, setIsNavigating] = useState(false); From a142d4ce88ae79aba4d50249e0d726463d4da8ae Mon Sep 17 00:00:00 2001 From: JunHam Date: Sun, 13 Jul 2025 01:17:45 +0800 Subject: [PATCH 11/12] Shifted cardItem card into new cardComponent --- src/components/Catalogue/cardComponent.jsx | 28 +++++++ src/components/Catalogue/cardItem.jsx | 85 ++++++---------------- 2 files changed, 51 insertions(+), 62 deletions(-) create mode 100644 src/components/Catalogue/cardComponent.jsx diff --git a/src/components/Catalogue/cardComponent.jsx b/src/components/Catalogue/cardComponent.jsx new file mode 100644 index 0000000..a16809e --- /dev/null +++ b/src/components/Catalogue/cardComponent.jsx @@ -0,0 +1,28 @@ +import { Card, Image, Text, Skeleton } from "@chakra-ui/react"; + +const CardComponent = ({ imageSrc, itemTitle, itemDescription, isLoading, setIsLoading, isSelected, expanded }) => { + return ( + + + {itemTitle} setIsLoading(false)} + /> + + + + {itemTitle} + + {expanded && ( + + {itemDescription} + + )} + + + ); +}; + +export default CardComponent; diff --git a/src/components/Catalogue/cardItem.jsx b/src/components/Catalogue/cardItem.jsx index 3d97d73..e66c22c 100644 --- a/src/components/Catalogue/cardItem.jsx +++ b/src/components/Catalogue/cardItem.jsx @@ -1,6 +1,7 @@ -import { Box, Card, Image, Text, useBreakpointValue, Skeleton } from "@chakra-ui/react"; +import { Box, useBreakpointValue } from "@chakra-ui/react"; import { useState, useRef, useEffect } from "react"; import { motion, AnimatePresence } from "framer-motion"; +import CardComponent from "./cardComponent"; const MotionBox = motion.create(Box); @@ -29,7 +30,6 @@ const CardItem = ({ itemTitle, itemDescription, selectedTitle, index }) => { const imageSrc = images[index % images.length]; - // Reset loading when image changes useEffect(() => { setIsLoading(true); }, [imageSrc]); @@ -89,20 +89,14 @@ const CardItem = ({ itemTitle, itemDescription, selectedTitle, index }) => { window.addEventListener("scroll", updateHoverPosition, true); window.addEventListener("resize", updateHoverPosition); if (scrollContainerRef.current) { - scrollContainerRef.current.addEventListener( - "scroll", - updateHoverPosition - ); + scrollContainerRef.current.addEventListener("scroll", updateHoverPosition); } return () => { window.removeEventListener("scroll", updateHoverPosition, true); window.removeEventListener("resize", updateHoverPosition); if (scrollContainerRef.current) { - scrollContainerRef.current.removeEventListener( - "scroll", - updateHoverPosition - ); + scrollContainerRef.current.removeEventListener("scroll", updateHoverPosition); } }; } @@ -123,25 +117,15 @@ const CardItem = ({ itemTitle, itemDescription, selectedTitle, index }) => { borderRadius="md" boxShadow={isSelected ? "lg" : "md"} > - - - {itemTitle} setIsLoading(false)} - /> - - - - {itemTitle} - - - + {!isMobile && ( @@ -165,40 +149,17 @@ const CardItem = ({ itemTitle, itemDescription, selectedTitle, index }) => { onMouseEnter={() => setIsHovered(true)} borderRadius="md" border={isSelected ? "1px solid" : "none"} - borderColor={ - isSelected ? "blue.600" : "transparent" - } - boxShadow={ - isSelected - ? "0 0 15px 2px rgba(66,153,225,0.6)" - : "2xl" - } + borderColor={isSelected ? "blue.600" : "transparent"} + boxShadow={isSelected ? "0 0 15px 2px rgba(66,153,225,0.6)" : "2xl"} > - - - {itemTitle} setIsLoading(false)} - /> - - - - {itemTitle} - - - {itemDescription} - - - + )} From 55953b110a2dd616dcb0c6f492f9a8e53a85571a Mon Sep 17 00:00:00 2001 From: JunHam Date: Sun, 13 Jul 2025 01:23:01 +0800 Subject: [PATCH 12/12] Fixed height for skeleton and image in skeleton --- src/components/Catalogue/cardComponent.jsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/Catalogue/cardComponent.jsx b/src/components/Catalogue/cardComponent.jsx index a16809e..53bcd47 100644 --- a/src/components/Catalogue/cardComponent.jsx +++ b/src/components/Catalogue/cardComponent.jsx @@ -3,12 +3,14 @@ import { Card, Image, Text, Skeleton } from "@chakra-ui/react"; const CardComponent = ({ imageSrc, itemTitle, itemDescription, isLoading, setIsLoading, isSelected, expanded }) => { return ( - + {itemTitle} setIsLoading(false)} + height="180px" + width="100%" />