Updates eslint and typescript-eslint dependencies. Migrates to a new portfolio structure with components, styling, route management, and internationalization. Adds project display with multiple project types using custom components. Adds image assets to be displayed in experience sections
76 lines
2.8 KiB
TypeScript
76 lines
2.8 KiB
TypeScript
import React, { useState, useEffect } from 'react';
|
|
import { motion, AnimatePresence } from 'framer-motion';
|
|
|
|
interface OverlayPictureProps {
|
|
src: string;
|
|
alt: string;
|
|
className?: string;
|
|
}
|
|
|
|
const OverlayPicture: React.FC<OverlayPictureProps> = ({ src, alt, className = '' }) => {
|
|
const [isOpen, setIsOpen] = useState(false);
|
|
|
|
useEffect(() => {
|
|
const handleEscape = (event: KeyboardEvent) => {
|
|
if (event.key === 'Escape' && isOpen) {
|
|
// We don't want the escape key event to propagate to the overlay parent
|
|
// If the picture overlay is used inside another overlay, this prevents both overlays from closing
|
|
event.stopPropagation();
|
|
setIsOpen(false);
|
|
}
|
|
};
|
|
|
|
if (isOpen) {
|
|
// Use capture phase
|
|
document.addEventListener('keydown', handleEscape, true);
|
|
document.body.style.overflow = 'hidden';
|
|
}
|
|
|
|
return () => {
|
|
document.removeEventListener('keydown', handleEscape, true);
|
|
document.body.style.overflow = 'unset';
|
|
};
|
|
}, [isOpen]);
|
|
|
|
return (
|
|
<>
|
|
<motion.div className={`${className} flex items-center justify-center`}>
|
|
<motion.img
|
|
src={src}
|
|
alt={alt}
|
|
className={`cursor-zoom-in flex self-center max-w-xl max-h-96 rounded-lg shadow-md object-contain`}
|
|
onClick={() => setIsOpen(true)}
|
|
transition={{ duration: 0.2 }}
|
|
layoutId='overlay-image'
|
|
/>
|
|
</motion.div>
|
|
|
|
<AnimatePresence>
|
|
{isOpen && (
|
|
<motion.div
|
|
initial={{ opacity: 0 }}
|
|
animate={{ opacity: 1 }}
|
|
exit={{ opacity: 0 }}
|
|
transition={{ duration: 0.2 }}
|
|
className="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-75 backdrop-blur-sm"
|
|
onClick={(e) => {
|
|
if (e.target === e.currentTarget) {
|
|
setIsOpen(false);
|
|
}
|
|
}}
|
|
>
|
|
<motion.img
|
|
src={src}
|
|
alt={alt}
|
|
className="max-w-[90%] max-h-[80%] object-contain rounded-md"
|
|
// We don't want the click on the image to close the overlay
|
|
layoutId='overlay-image'
|
|
/>
|
|
</motion.div>
|
|
)}
|
|
</AnimatePresence>
|
|
</>
|
|
);
|
|
};
|
|
|
|
export default OverlayPicture; |