veza/apps/web/src/components/ui/ImageViewerModal.tsx
senke 6974c12a25 aesthetic-improvements: align spacing to 8px grid (Action 11.2.1.3)
- Created automated script (scripts/align-8px-grid.py) to align all spacing to 8px grid
- Replaced non-8px-aligned spacing: gap-3/p-3/m-3 (12px) → gap-4/p-4/m-4 (16px), gap-5/p-5/m-5 (20px) → gap-6/p-6/m-6 (24px), gap-10/p-10/m-10 (40px) → gap-12/p-12/m-12 (48px), gap-20/p-20/m-20 (80px) → gap-24/p-24/m-24 (96px)
- Preserved: 4px values (gap-1, p-1, m-1) as they may be intentional fine-tuning, responsive breakpoints (sm:, md:, lg:), test files, documentation
- Modified files across all components to ensure consistent 8px grid alignment
- Action 11.2.1.3: Align all elements to 8px grid - COMPLETE
2026-01-16 11:50:46 +01:00

147 lines
3.7 KiB
TypeScript

import React from 'react';
import { X, ChevronLeft, ChevronRight, Download } from 'lucide-react';
/**
* ImageViewerModalProps - Propriétés du composant ImageViewerModal
*
* @interface ImageViewerModalProps
*/
interface ImageViewerModalProps {
/**
* URL de l'image à afficher
*/
src: string;
/**
* Texte alternatif de l'image
*/
alt?: string;
/**
* Fonction appelée pour fermer le visualiseur
*/
onClose: () => void;
/**
* Fonction appelée pour passer à l'image suivante
*/
onNext?: () => void;
/**
* Fonction appelée pour passer à l'image précédente
*/
onPrev?: () => void;
/**
* Si `true`, affiche le bouton suivant
*/
hasNext?: boolean;
/**
* Si `true`, affiche le bouton précédent
*/
hasPrev?: boolean;
}
/**
* ImageViewerModal - Composant de visualisation d'image en plein écran
*
* Composant modal pour afficher une image en plein écran avec :
* - Navigation entre images (précédent/suivant)
* - Téléchargement de l'image
* - Fermeture avec bouton ou Escape
* - Fond sombre avec backdrop blur
*
* @example
* ```tsx
* // Visualiseur simple
* <ImageViewerModal
* src={imageUrl}
* alt="Description"
* onClose={() => setShowViewer(false)}
* />
* ```
*
* @example
* ```tsx
* // Avec navigation
* <ImageViewerModal
* src={images[currentIndex]}
* alt={`Image ${currentIndex + 1}`}
* onClose={() => setShowViewer(false)}
* onNext={() => setCurrentIndex(i => i + 1)}
* onPrev={() => setCurrentIndex(i => i - 1)}
* hasNext={currentIndex < images.length - 1}
* hasPrev={currentIndex > 0}
* />
* ```
*
* @component
* @param {ImageViewerModalProps} props - Propriétés du composant
* @returns {JSX.Element} Modal plein écran avec image et contrôles
*/
export const ImageViewerModal: React.FC<ImageViewerModalProps> = ({
src,
alt,
onClose,
onNext,
onPrev,
hasNext,
hasPrev,
}) => {
return (
<div className="fixed inset-0 z-[200] bg-black/95 backdrop-blur-xl flex items-center justify-center animate-fadeIn">
{/* Toolbar */}
<div className="absolute top-0 left-0 right-0 p-4 flex justify-between items-center z-10 bg-gradient-to-b from-black/50 to-transparent">
<span className="text-white/50 text-sm font-mono">
{alt || 'Image Preview'}
</span>
<div className="flex gap-4">
<a
href={src}
download
className="p-2 bg-white/10 rounded-full hover:bg-white/20 text-white transition-colors"
title="Download"
>
<Download className="w-5 h-5" />
</a>
<button
onClick={onClose}
className="p-2 bg-white/10 rounded-full hover:bg-white/20 text-white transition-colors"
title="Close"
>
<X className="w-5 h-5" />
</button>
</div>
</div>
{/* Navigation */}
{hasPrev && (
<button
onClick={onPrev}
className="absolute left-4 p-4 bg-white/10 rounded-full hover:bg-white/20 text-white transition-colors z-10"
>
<ChevronLeft className="w-8 h-8" />
</button>
)}
{hasNext && (
<button
onClick={onNext}
className="absolute right-4 p-4 bg-white/10 rounded-full hover:bg-white/20 text-white transition-colors z-10"
>
<ChevronRight className="w-8 h-8" />
</button>
)}
{/* Image */}
<div className="w-full h-full p-4 md:p-10 flex items-center justify-center">
<img
src={src}
alt={alt}
className="max-w-full max-h-full object-contain shadow-2xl rounded-lg"
/>
</div>
</div>
);
};