158 lines
4.7 KiB
Markdown
158 lines
4.7 KiB
Markdown
# ExpandedImageView Component
|
||
|
||
Displays full-size images in modal overlays with loading, error, and success states.
|
||
|
||
## Features
|
||
|
||
- **Loading State:** Purple spinner with descriptive text
|
||
- **Error State:** Clear error message with retry button
|
||
- **Success State:** Smooth fade-in transition
|
||
- **Optional Metadata:** Display filename, dimensions, and file size
|
||
- **Responsive Padding:** Adapts to mobile, tablet, and desktop
|
||
- **Accessibility:** ARIA live regions, semantic roles, keyboard support
|
||
|
||
## Props
|
||
|
||
```typescript
|
||
interface ExpandedImageViewProps {
|
||
imageUrl: string; // Required: Image URL to display
|
||
alt?: string; // Optional: Alt text (default: 'Full size view')
|
||
metadata?: { // Optional: Image metadata
|
||
filename?: string; // e.g., 'sunset_1024x768.png'
|
||
size?: string; // e.g., '2.4 MB'
|
||
dimensions?: string; // e.g., '1024 × 768'
|
||
};
|
||
showMetadata?: boolean; // Optional: Show metadata bar (default: false)
|
||
}
|
||
```
|
||
|
||
## Usage Examples
|
||
|
||
### Basic Usage
|
||
```tsx
|
||
import { ExpandedImageView } from '@/components/shared/ExpandedImageView';
|
||
|
||
export default function ImageModal({ imageUrl }: { imageUrl: string }) {
|
||
return (
|
||
<div className="fixed inset-0 bg-black/90 z-50">
|
||
<ExpandedImageView
|
||
imageUrl={imageUrl}
|
||
alt="Generated landscape image"
|
||
/>
|
||
</div>
|
||
);
|
||
}
|
||
```
|
||
|
||
### With Metadata
|
||
```tsx
|
||
import { ExpandedImageView } from '@/components/shared/ExpandedImageView';
|
||
|
||
export default function ImageGalleryModal({ image }: { image: Image }) {
|
||
return (
|
||
<div className="fixed inset-0 bg-black/90 z-50">
|
||
<ExpandedImageView
|
||
imageUrl={image.url}
|
||
alt={image.prompt}
|
||
showMetadata
|
||
metadata={{
|
||
filename: image.filename,
|
||
dimensions: `${image.width} × ${image.height}`,
|
||
size: formatFileSize(image.sizeBytes)
|
||
}}
|
||
/>
|
||
</div>
|
||
);
|
||
}
|
||
```
|
||
|
||
### In Page Provider System
|
||
```tsx
|
||
'use client';
|
||
|
||
import { useState } from 'react';
|
||
import { ExpandedImageView } from '@/components/shared/ExpandedImageView';
|
||
import { CompactFooter } from '@/components/shared/CompactFooter';
|
||
|
||
export default function ImageViewerPage() {
|
||
const [selectedImage, setSelectedImage] = useState<string | null>(null);
|
||
|
||
return (
|
||
<>
|
||
{/* Gallery */}
|
||
<div className="grid grid-cols-3 gap-4 p-6">
|
||
{images.map((img) => (
|
||
<img
|
||
key={img.id}
|
||
src={img.thumbnail}
|
||
onClick={() => setSelectedImage(img.fullUrl)}
|
||
className="cursor-pointer hover:opacity-80"
|
||
/>
|
||
))}
|
||
</div>
|
||
|
||
{/* Overlay */}
|
||
{selectedImage && (
|
||
<div
|
||
className="fixed inset-0 bg-black/90 z-50 flex flex-col"
|
||
onClick={() => setSelectedImage(null)}
|
||
>
|
||
<nav className="h-16 border-b border-white/10">
|
||
<button onClick={() => setSelectedImage(null)}>Close</button>
|
||
</nav>
|
||
|
||
<main className="flex-1 overflow-auto">
|
||
<ExpandedImageView
|
||
imageUrl={selectedImage}
|
||
alt="Expanded gallery image"
|
||
showMetadata
|
||
/>
|
||
</main>
|
||
|
||
<CompactFooter />
|
||
</div>
|
||
)}
|
||
</>
|
||
);
|
||
}
|
||
```
|
||
|
||
## Accessibility Features
|
||
|
||
### ARIA Roles
|
||
- **Loading State:** `role="status"` with `aria-live="polite"`
|
||
- **Error State:** `role="alert"` with `aria-live="assertive"`
|
||
- **Icon Elements:** `aria-hidden="true"` (decorative only)
|
||
|
||
### Keyboard Support
|
||
- Retry button is keyboard accessible (Enter/Space)
|
||
- Image click event stops propagation (prevents modal close)
|
||
- Integrates with modal close handlers (Escape key)
|
||
|
||
### Visual Design
|
||
- **Loading:** Purple spinner (`border-purple-600`) matches Banatie design
|
||
- **Error:** Red color scheme (`red-900/20`, `red-700/50`, `red-400`)
|
||
- **Image:** Shadow (`shadow-2xl`) and rounded corners (`rounded-lg`)
|
||
- **Metadata:** Subtle bar with Banatie card styling
|
||
|
||
## Layout Constraints
|
||
|
||
- **Max Image Height:** `calc(100vh - 12rem)` reserves space for nav/footer
|
||
- **Object Fit:** `object-contain` maintains aspect ratio
|
||
- **Responsive Padding:** `p-4 sm:p-6 md:p-8`
|
||
- **Metadata Wrapping:** Flexbox with `flex-wrap` for mobile
|
||
|
||
## Performance
|
||
|
||
- **Lazy State Management:** Only renders visible state (loading/error/success)
|
||
- **Event Handlers:** Optimized with direct callbacks
|
||
- **Image Loading:** Native browser lazy loading via `onLoad`/`onError`
|
||
- **Transition:** CSS-only fade animation (no JavaScript)
|
||
|
||
## Error Handling
|
||
|
||
- **Network Failures:** Catches `onerror` events
|
||
- **User Retry:** Resets state and triggers re-render
|
||
- **Clear Messaging:** Friendly error text with actionable button
|
||
- **Visual Feedback:** Icon and color coding for quick recognition
|