diff --git a/src/components/Catalogue/cardComponent.jsx b/src/components/Catalogue/cardComponent.jsx new file mode 100644 index 0000000..53bcd47 --- /dev/null +++ b/src/components/Catalogue/cardComponent.jsx @@ -0,0 +1,30 @@ +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%" + /> + + + + {itemTitle} + + {expanded && ( + + {itemDescription} + + )} + + + ); +}; + +export default CardComponent; diff --git a/src/components/Catalogue/cardItem.jsx b/src/components/Catalogue/cardItem.jsx index 71890d8..e66c22c 100644 --- a/src/components/Catalogue/cardItem.jsx +++ b/src/components/Catalogue/cardItem.jsx @@ -1,7 +1,7 @@ -// CardItem.jsx -import { Box, Card, Image, Text } from "@chakra-ui/react"; -import React, { useState, useRef, useEffect } from "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); @@ -13,8 +13,12 @@ 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 [isLoading, setIsLoading] = useState(true); + const cardRef = useRef(null); const [hoverPos, setHoverPos] = useState({ top: 0, left: 0 }); const scrollContainerRef = useRef(null); @@ -24,6 +28,12 @@ const CardItem = ({ itemTitle, itemDescription, selectedTitle, index }) => { setIsSelected(selectedTitle === itemTitle); }, [selectedTitle, itemTitle]); + const imageSrc = images[index % images.length]; + + useEffect(() => { + setIsLoading(true); + }, [imageSrc]); + const updateHoverPosition = () => { const rect = cardRef.current?.getBoundingClientRect(); if (rect) { @@ -47,6 +57,7 @@ const CardItem = ({ itemTitle, itemDescription, selectedTitle, index }) => { }; const handleMouseEnter = () => { + if (isMobile) return; hoverTimeoutRef.current = setTimeout(() => { updateHoverPosition(); setIsHovered(true); @@ -59,7 +70,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,26 +89,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]); - - const imageSrc = images[index % images.length]; + }, [isHovered, isMobile]); return ( <> @@ -114,55 +117,53 @@ const CardItem = ({ itemTitle, itemDescription, selectedTitle, index }) => { borderRadius="md" boxShadow={isSelected ? "lg" : "md"} > - - {itemTitle} - - {itemTitle} - - + - - {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"} + > + + + )} + + )} ); }; diff --git a/src/components/Catalogue/catalogueItemView.jsx b/src/components/Catalogue/catalogueItemView.jsx index 20f2e9b..399affa 100644 --- a/src/components/Catalogue/catalogueItemView.jsx +++ b/src/components/Catalogue/catalogueItemView.jsx @@ -8,25 +8,32 @@ const MotionChevron = motion.create(Box); const CatalogueItemView = ({ isOpen, onClose, title, items, setDialogTitle, imageSrc }) => { const [isNavigating, setIsNavigating] = useState(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); const currentIndex = items.findIndex((item) => item.title === title); const prevItem = () => { - if (currentIndex > 0) { + if (!isNavigating && currentIndex > 0) { + setDirection(-1); setIsNavigating(true); setDialogTitle(items[currentIndex - 1].title); } }; const nextItem = () => { - if (currentIndex < items.length - 1) { + if (!isNavigating && currentIndex < items.length - 1) { + setDirection(1); setIsNavigating(true); setDialogTitle(items[currentIndex + 1].title); } }; - // Lock body scroll when modal is open + // Prevent background scroll when modal is open useEffect(() => { document.body.style.overflow = isOpen ? "hidden" : ""; return () => { @@ -34,7 +41,7 @@ const CatalogueItemView = ({ isOpen, onClose, title, items, setDialogTitle, imag }; }, [isOpen]); - // Reset isNavigating after item change animation + // Allow new navigation after animation ends useEffect(() => { if (isNavigating) { const timeout = setTimeout(() => setIsNavigating(false), 250); @@ -42,7 +49,53 @@ const CatalogueItemView = ({ isOpen, onClose, title, items, setDialogTitle, imag } }, [title]); - // Responsive values + // Reset flags when modal closes + useEffect(() => { + if (!isOpen) { + setHasRendered(false); + setDirection(0); + setIsInitialRender(true); + } + }, [isOpen]); + + // Scroll to correct position on mobile when rendered + useEffect(() => { + if (isMobile && isOpen && hasRendered && scrollRef.current) { + scrollRef.current.scrollTo({ + left: currentIndex * window.innerWidth, + behavior: "instant", + }); + } + }, [isMobile, isOpen, hasRendered, currentIndex]); + + // Keyboard navigation + 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]); + + // Focus modal when open + useEffect(() => { + 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(); + } else if (e.key === "ArrowRight" && currentIndex < items.length - 1) { + e.preventDefault(); + nextItem(); + } + }; + const modalSize = { width: isMobile ? "100vw" : "80vw", height: isMobile ? "100vh" : "80vh", @@ -51,11 +104,17 @@ 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"; + 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 ( @@ -78,72 +137,69 @@ const CatalogueItemView = ({ isOpen, onClose, title, items, setDialogTitle, imag onClick={onClose} /> - {/* Left Chevron */} - - {currentIndex > 0 && ( - - - - )} - - - {/* Right Chevron */} - - {currentIndex < items.length - 1 && ( - - - - )} - - - {/* Modal Wrapper */} + {/* Desktop chevrons */} + {!isMobile && ( + <> + {currentIndex > 0 && ( + + + + )} + + {currentIndex < items.length - 1 && ( + + + + )} + + )} + + {/* Modal content */} e.stopPropagation()} > - - {/* Image Section */} - - {title} + {items.map((item) => ( + + + {item.title} + + + + + {item.title} + + + + ))} + {!hasRendered && ( + setHasRendered(true)} /> + )} - - {/* Content Section */} - - - - {title} - - {/* Add your content here */} + ) : ( + // Desktop: sliding animation + + + { + if (isInitialRender) setIsInitialRender(false); + }} + > + + {title} + + + + + {title} + + + + - + )} @@ -231,4 +333,4 @@ const CatalogueItemView = ({ isOpen, onClose, title, items, setDialogTitle, imag ); }; -export default CatalogueItemView; \ No newline at end of file +export default CatalogueItemView; diff --git a/src/components/Catalogue/mmSection.jsx b/src/components/Catalogue/mmSection.jsx index cb6e3a5..d7a40d4 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,25 +46,28 @@ const MMSection = ({ onItemClick, selectedTitle }) => { }); }; + const handleClick = (title) => { + if (onItemClick) onItemClick(title); + }; + return ( - + Meeting Minutes - scroll("left")} /> - scroll("right")} /> + {!isMobile && ( + <> + scroll("left")} /> + scroll("right")} /> + + )} - + {items.map((item, index) => ( { - // 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,36 +66,34 @@ 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")} + /> + + )} - + {items.map((item, index) => ( { - handleCardClick(item.title) - } + onClick={() => handleCardClick(item.title)} > { ))} -