@mgonza02/goal-image-gallery
v1.1.1
Published
Advanced, accessible, and highly customizable React image gallery component with multi-method upload, responsive design, and extensive TypeScript support
Maintainers
Readme
GoalImageGallery Component Documentation
Overview
The GoalImageGallery is a modern, feature-rich image gallery component for React applications. It provides a comprehensive solution for displaying, managing, and uploading images with advanced UI/UX features, accessibility support, and extensive customization options. This component is designed to be highly modular, performant, and easy to integrate into any React application.
🚀 Quick Start
npm install @mgonza02/goal-image-galleryimport { GoalImageGallery } from '@mgonza02/goal-image-gallery';
// Basic usage
<GoalImageGallery imageCodes={['img1', 'img2']} canEdit={false} />Key Features
🖼️ Multi-Method Image Upload
- File Upload: Traditional file selection with drag-and-drop support
- Clipboard Paste: Paste images directly from clipboard using Ctrl+V
- URL Loading: Load images from remote URLs
- Batch Upload: Upload multiple images simultaneously
🎨 Modern UI/UX
- Responsive Design: Adapts to all screen sizes and devices
- Material Design 3: Latest Material-UI components and styling
- Smooth Animations: Hover effects, transitions, and loading states
- Dark/Light Theme: Automatic theme detection and support
- Accessibility: WCAG 2.1 AA compliant with full keyboard navigation
🔍 Advanced Image Viewer
- Full-Screen Modal: Immersive image viewing experience
- Zoom Controls: Zoom from 0.5x to 3x with smooth transitions
- Image Navigation: Previous/next with keyboard shortcuts
- Download Support: Direct image download functionality
- Image Information: Display metadata and details
🛠️ Customization & Extensibility
- Slot System: Replace default components with custom implementations
- Flexible Props: Extensive configuration options
- TypeScript Support: Full type definitions included
- Hook-Based Architecture: Reusable custom hooks for complex logic
📱 Mobile-First Design
- Touch Gestures: Swipe, pinch-to-zoom, and tap interactions
- Responsive Breakpoints: Optimized layouts for all devices
- Performance Optimized: Lazy loading and efficient rendering
- Offline Support: Graceful degradation for poor connectivity
Installation & Setup
NPM Installation
npm install @mgonza02/goal-image-galleryPeer Dependencies
Make sure you have the required peer dependencies installed:
npm install @mui/material @mui/icons-material @emotion/react @emotion/styled react react-domImport
// Main component
import { GoalImageGallery } from '@mgonza02/goal-image-gallery';
// Individual components and hooks
import {
ImageGalleryGrid,
ImageGalleryModal,
useImageGallery,
useImageZoom
} from '@mgonza02/goal-image-gallery';TypeScript Support
The component includes full TypeScript definitions:
import { GoalImageGalleryProps } from '@mgonza02/goal-image-gallery';
const MyComponent: React.FC<{ images: string[] }> = ({ images }) => {
return (
<GoalImageGallery
imageCodes={images}
canEdit={true}
// TypeScript will provide full autocomplete and type checking
/>
);
};Usage Examples
1. Basic Read-Only Gallery
Perfect for displaying images without editing capabilities:
<GoalImageGallery
imageCodes={['img1', 'img2', 'img3']}
canEdit={false}
emptyMessage="No images to display"
getImageUrl={({ imageCode }) => `https://api.example.com/images/${imageCode}`}
/>2. Editable Product Gallery
Full-featured gallery with upload, edit, and delete capabilities:
const ProductGallery = () => {
const [productImages, setProductImages] = useState(['prod1', 'prod2']);
const handleImageUpload = async (imageData) => {
try {
const response = await api.uploadProductImage({
...imageData,
productId: product.id,
companyId: currentCompany
});
return {
success: true,
data: response.data,
message: 'Images uploaded successfully'
};
} catch (error) {
return {
success: false,
message: error.message
};
}
};
const refreshProduct = (result) => {
// Update product data
setProductImages(prev => [...prev, result.data.code]);
};
return (
<GoalImageGallery
imageCodes={productImages}
canEdit={true}
ownerEntity={{ id: product.id, type: 'product' }}
imageHandlerApi={handleImageUpload}
afterUpload={refreshProduct}
multiple={true}
emptyMessage="No product images available"
showImageInfo={true}
allowDownload={true}
permission="EDIT_PRODUCTS"
hasPermission={(perm) => userPermissions.includes(perm)}
currentCompany={user.companyId}
/>
);
};3. User Avatar Gallery
Single image gallery for user avatars:
<GoalImageGallery
imageCodes={user.avatar}
canEdit={isOwner}
ownerEntity={{ id: user.id, type: 'user' }}
imageHandlerApi={userImageApi.upload}
afterUpload={refreshUser}
multiple={false}
emptyMessage="No avatar set"
showImageInfo={false}
getImageUrl={({ imageCode }) => `https://cdn.example.com/avatars/${imageCode}`}
/>4. Custom Image Selector
Using the slot system to provide a custom image selector:
// Custom selector component
const CustomImageSelector = ({
afterUpload,
activate,
onCancel,
multiple,
title,
uploadMessage,
maxFileSize,
acceptedFormats,
customStyles
}) => {
const [dragActive, setDragActive] = useState(false);
const [uploading, setUploading] = useState(false);
const handleFiles = async (files) => {
setUploading(true);
try {
// Custom upload logic
const result = await uploadFiles(files);
afterUpload(result);
} catch (error) {
console.error('Upload failed:', error);
} finally {
setUploading(false);
}
};
return (
<div
style={{
...customStyles,
border: dragActive ? '2px solid #007bff' : '2px dashed #ccc',
borderRadius: '8px',
padding: '40px',
textAlign: 'center',
backgroundColor: dragActive ? '#f8f9fa' : '#fff'
}}
onDragOver={(e) => {
e.preventDefault();
setDragActive(true);
}}
onDragLeave={() => setDragActive(false)}
onDrop={async (e) => {
e.preventDefault();
setDragActive(false);
const files = Array.from(e.dataTransfer.files);
await handleFiles(files);
}}
>
<h3>{title}</h3>
<p>{uploadMessage}</p>
{uploading && <p>Uploading...</p>}
<input
type="file"
multiple={multiple}
accept={acceptedFormats?.join(',')}
onChange={(e) => handleFiles(Array.from(e.target.files))}
style={{ margin: '10px 0' }}
/>
<br />
<button onClick={onCancel} disabled={uploading}>
Cancel
</button>
</div>
);
};
// Usage with custom selector
<GoalImageGallery
imageCodes={images}
canEdit={true}
ownerEntity={{ id: 'entity-123' }}
imageHandlerApi={handleUpload}
afterUpload={refreshData}
multiple={true}
slot={{
selector: CustomImageSelector
}}
slotProps={{
selector: {
maxFileSize: 10 * 1024 * 1024, // 10MB
acceptedFormats: ['image/jpeg', 'image/png', 'image/webp'],
customStyles: {
borderColor: '#007bff',
backgroundColor: '#f8f9fa'
}
}
}}
/>ownerEntity={{ id: 'product-123', type: 'product' }} imageHandlerApi={handleImageUpload} afterUpload={refreshImages} multiple={true} slot={{ selector: CustomImageSelector }} slotProps={{ selector: { maxFileSize: 10 * 1024 * 1024, // 10MB acceptedFormats: ['image/jpeg', 'image/png'], uploadEndpoint: '/api/upload', customStyles: { borderColor: '#007bff' } } }} />
## Props Reference
### Core Props
| Prop | Type | Default | Required | Description |
|------|------|---------|----------|-------------|
| `imageCodes` | `string \| string[]` | `[]` | No | Image codes to display in the gallery |
| `canEdit` | `boolean` | `false` | No | Enable editing capabilities (upload, delete) |
| `ownerEntity` | `object` | `undefined` | No | Entity that owns the images (required for editing) |
| `imageHandlerApi` | `function` | `undefined` | No | API function for image operations (required for editing) |
| `afterUpload` | `function` | `undefined` | No | Callback function called after successful upload |
| `multiple` | `boolean` | `false` | No | Allow multiple image selection and upload |
### Display Props
| Prop | Type | Default | Required | Description |
|------|------|---------|----------|-------------|
| `emptyMessage` | `string` | `'No hay imágenes para mostrar'` | No | Message displayed when no images are available |
| `showImageInfo` | `boolean` | `false` | No | Show image information overlay on hover |
| `allowDownload` | `boolean` | `false` | No | Enable image download functionality |
| `getImageUrl` | `function` | `({ imageCode }) => '/images/${imageCode}'` | No | Function to generate image URLs from codes |
| `noImageUrl` | `string` | `'/images/no-image.png'` | No | Fallback image URL for broken images |
### Permission Props
| Prop | Type | Default | Required | Description |
|------|------|---------|----------|-------------|
| `permission` | `string` | `undefined` | No | Required permission string for editing |
| `hasPermission` | `function` | `() => true` | No | Function to check if user has required permission |
| `currentCompany` | `string` | `'default'` | No | Current company identifier for API calls |
### Customization Props
| Prop | Type | Default | Required | Description |
|------|------|---------|----------|-------------|
| `slot` | `object` | `{}` | No | Slot components for customization |
| `slotProps` | `object` | `{}` | No | Props passed to slot components |
| `showError` | `function` | `console.error` | No | Function to display error messages |
### Advanced Props
| Prop | Type | Default | Required | Description |
|------|------|---------|----------|-------------|
| `maxFileSize` | `number` | `25 * 1024 * 1024` | No | Maximum file size in bytes (25MB default) |
| `acceptedFormats` | `string[]` | `['image/jpeg', 'image/png', 'image/gif', 'image/webp']` | No | Accepted image formats |
| `thumbnailSize` | `number` | `200` | No | Thumbnail size in pixels |
| `gridColumns` | `number` | `auto` | No | Number of columns in grid (auto-calculated if not provided) |
| `enableLazyLoading` | `boolean` | `true` | No | Enable lazy loading for images |
| `enableVirtualization` | `boolean` | `false` | No | Enable virtual scrolling for large image sets |
### Prop Examples
```javascript
// Basic configuration
<GoalImageGallery
imageCodes={['img1', 'img2', 'img3']}
canEdit={false}
/>
// Full configuration
<GoalImageGallery
imageCodes={product.images}
canEdit={user.canEdit}
ownerEntity={{ id: product.id, type: 'product' }}
imageHandlerApi={async (data) => {
const response = await api.uploadImage(data);
return { success: true, data: response };
}}
afterUpload={(result) => {
console.log('Upload successful:', result);
refreshProduct();
}}
multiple={true}
permission="EDIT_PRODUCTS"
emptyMessage="No product images yet"
showImageInfo={true}
allowDownload={true}
getImageUrl={({ imageCode, thumbMail }) =>
`https://api.example.com/images/${imageCode}${thumbMail ? '?thumb=true' : ''}`
}
noImageUrl="https://api.example.com/images/placeholder.png"
hasPermission={(perm) => user.permissions.includes(perm)}
currentCompany={user.companyId}
showError={(msg) => toast.error(msg)}
maxFileSize={10 * 1024 * 1024}
acceptedFormats={['image/jpeg', 'image/png']}
thumbnailSize={150}
gridColumns={3}
/>Upload Methods & Features
1. File Upload (Drag & Drop)
The most common method for uploading images:
// Features:
// - Click to select files
// - Drag and drop support
// - Multiple file selection
// - File type validation
// - Size limit enforcement
// - Progress indication
<GoalImageGallery
canEdit={true}
multiple={true}
maxFileSize={10 * 1024 * 1024} // 10MB
acceptedFormats={['image/jpeg', 'image/png', 'image/webp']}
// ... other props
/>Supported Features:
- File Types: JPEG, PNG, GIF, WebP, SVG
- Size Limits: Configurable (default: 25MB)
- Batch Upload: Upload multiple files simultaneously
- Progress Tracking: Visual progress indicators
- Error Handling: File validation and error reporting
2. Clipboard Paste
Revolutionary clipboard image support:
// Usage:
// 1. Copy image from any source (screenshot, browser, etc.)
// 2. Focus on the gallery
// 3. Press Ctrl+V (Cmd+V on Mac)
// 4. Image is automatically processed and uploaded
// Auto-enabled when canEdit={true}
<GoalImageGallery
canEdit={true}
// Clipboard paste is automatically available
/>Clipboard Features:
- Cross-Platform: Works on Windows, Mac, and Linux
- Source Agnostic: Copy from any application
- Auto-Processing: Automatic image processing and optimization
- Format Support: All major image formats
- Browser Support: Modern browsers (Chrome, Firefox, Safari, Edge)
3. URL Loading
Load images from external URLs:
// Users can enter image URLs directly
// Component validates and loads the image
// Supports CORS-enabled images
const handleUrlUpload = async (imageUrl) => {
try {
const response = await fetch(imageUrl);
const blob = await response.blob();
// Process the image
const result = await processImageBlob(blob);
return {
success: true,
data: result
};
} catch (error) {
return {
success: false,
message: 'Failed to load image from URL'
};
}
};4. Mobile Camera Integration
Enhanced mobile support:
// Mobile-specific features:
// - Camera access
// - Photo library access
// - Touch gestures
// - Responsive design
<GoalImageGallery
canEdit={true}
// Mobile camera is automatically available on mobile devices
mobileOptimized={true}
touchGestures={true}
/>Keyboard Shortcuts & Navigation
Image Viewer Shortcuts
| Shortcut | Action | Context |
|----------|---------|---------|
| ← / → | Navigate between images | Image viewer open |
| + / = | Zoom in | Image viewer open |
| - | Zoom out | Image viewer open |
| Escape | Close image viewer | Image viewer open |
| Space | Toggle fullscreen | Image viewer open |
| Enter | Open selected image | Gallery focused |
Gallery Navigation
| Shortcut | Action | Context |
|----------|---------|---------|
| Tab | Navigate between elements | Gallery focused |
| Shift + Tab | Navigate backwards | Gallery focused |
| Enter / Space | Activate button/link | Element focused |
| Ctrl + V | Paste from clipboard | Editing enabled |
| Delete | Remove selected image | Image selected |
Accessibility Features
// Built-in accessibility features:
<GoalImageGallery
// ARIA labels are automatically generated
ariaLabel="Product image gallery"
ariaDescribedBy="gallery-instructions"
// Screen reader support
screenReaderOptimized={true}
// High contrast mode
highContrastMode={true}
// Keyboard navigation
keyboardNavigation={true}
// Focus management
manageFocus={true}
/>Custom Keyboard Shortcuts
import { useKeyboardNavigation } from '@mgonza02/goal-image-gallery';
const CustomGallery = () => {
const { handleKeyPress } = useKeyboardNavigation({
onPreviousImage: () => console.log('Previous'),
onNextImage: () => console.log('Next'),
onCloseImage: () => console.log('Close'),
onZoomIn: () => console.log('Zoom in'),
onZoomOut: () => console.log('Zoom out'),
// Add custom shortcuts
customShortcuts: {
'd': () => downloadCurrentImage(),
'i': () => showImageInfo(),
'f': () => toggleFullscreen()
}
});
return (
<div onKeyDown={handleKeyPress}>
<GoalImageGallery {...props} />
</div>
);
};API Integration & Data Flow
API Handler Function
The imageHandlerApi function is the core of the upload system:
const handleImageUpload = async (imageData) => {
try {
// imageData contains:
// - files: Array of File objects
// - metadata: Additional data (owner, company, etc.)
// - options: Upload options (compression, format, etc.)
const response = await api.uploadImages({
files: imageData.files,
companyId: imageData.metadata.companyId,
entityId: imageData.metadata.entityId,
entityType: imageData.metadata.entityType,
folder: imageData.options.folder,
compression: imageData.options.compression,
thumbnailSize: imageData.options.thumbnailSize
});
// Must return standardized response
return {
success: true,
data: response.data, // Array of uploaded image objects
message: 'Images uploaded successfully',
codes: response.data.map(img => img.code) // Optional: image codes
};
} catch (error) {
return {
success: false,
message: error.message || 'Upload failed',
error: error
};
}
};API Response Format
Expected response format from your API:
// Successful response
{
success: true,
data: [
{
id: 123,
code: 'img_abc123',
url: 'https://cdn.example.com/images/img_abc123.jpg',
thumbnailUrl: 'https://cdn.example.com/images/img_abc123_thumb.jpg',
filename: 'product-image.jpg',
size: 1024000,
width: 1920,
height: 1080,
mimeType: 'image/jpeg',
uploadedAt: '2023-01-01T00:00:00Z'
}
],
message: 'Upload successful'
}
// Error response
{
success: false,
message: 'File size too large',
error: {
code: 'FILE_TOO_LARGE',
details: 'Maximum file size is 25MB'
}
}Real-World API Integration Examples
1. REST API Integration
const restApiHandler = async (imageData) => {
const formData = new FormData();
// Add files
imageData.files.forEach((file, index) => {
formData.append(`files[${index}]`, file);
});
// Add metadata
formData.append('companyId', imageData.metadata.companyId);
formData.append('entityId', imageData.metadata.entityId);
formData.append('entityType', imageData.metadata.entityType);
try {
const response = await fetch('/api/images/upload', {
method: 'POST',
body: formData,
headers: {
'Authorization': `Bearer ${authToken}`,
// Don't set Content-Type, let browser set it with boundary
}
});
const result = await response.json();
if (!response.ok) {
throw new Error(result.message || 'Upload failed');
}
return {
success: true,
data: result.images,
message: result.message
};
} catch (error) {
return {
success: false,
message: error.message
};
}
};2. GraphQL Integration
import { useMutation } from '@apollo/client';
const UPLOAD_IMAGES = gql`
mutation UploadImages($files: [Upload!]!, $input: ImageUploadInput!) {
uploadImages(files: $files, input: $input) {
success
message
images {
id
code
url
thumbnailUrl
filename
size
width
height
}
}
}
`;
const GraphQLImageGallery = ({ ownerEntity, ...props }) => {
const [uploadImages] = useMutation(UPLOAD_IMAGES);
const handleImageUpload = async (imageData) => {
try {
const { data } = await uploadImages({
variables: {
files: imageData.files,
input: {
companyId: imageData.metadata.companyId,
entityId: imageData.metadata.entityId,
entityType: imageData.metadata.entityType
}
}
});
return {
success: data.uploadImages.success,
data: data.uploadImages.images,
message: data.uploadImages.message
};
} catch (error) {
return {
success: false,
message: error.message
};
}
};
return (
<GoalImageGallery
{...props}
ownerEntity={ownerEntity}
imageHandlerApi={handleImageUpload}
/>
);
};3. Firebase Storage Integration
import { ref, uploadBytes, getDownloadURL } from 'firebase/storage';
import { storage } from './firebaseConfig';
const firebaseHandler = async (imageData) => {
try {
const uploadPromises = imageData.files.map(async (file) => {
const storageRef = ref(storage, `images/${Date.now()}_${file.name}`);
const snapshot = await uploadBytes(storageRef, file);
const downloadURL = await getDownloadURL(snapshot.ref);
return {
id: snapshot.ref.fullPath,
code: snapshot.ref.name,
url: downloadURL,
filename: file.name,
size: file.size,
mimeType: file.type
};
});
const results = await Promise.all(uploadPromises);
return {
success: true,
data: results,
message: 'Images uploaded to Firebase'
};
} catch (error) {
return {
success: false,
message: error.message
};
}
};Error Handling Best Practices
const robustApiHandler = async (imageData) => {
try {
// Validate files before upload
const validFiles = imageData.files.filter(file => {
if (file.size > 25 * 1024 * 1024) {
console.warn(`File ${file.name} is too large`);
return false;
}
if (!file.type.startsWith('image/')) {
console.warn(`File ${file.name} is not an image`);
return false;
}
return true;
});
if (validFiles.length === 0) {
return {
success: false,
message: 'No valid files to upload'
};
}
// Attempt upload with retry logic
let attempts = 0;
const maxAttempts = 3;
while (attempts < maxAttempts) {
try {
const response = await uploadFiles(validFiles);
return {
success: true,
data: response.data,
message: 'Upload successful'
};
} catch (error) {
attempts++;
if (attempts >= maxAttempts) {
throw error;
}
// Wait before retry
await new Promise(resolve => setTimeout(resolve, 1000 * attempts));
}
}
} catch (error) {
// Log error for debugging
console.error('Image upload failed:', error);
return {
success: false,
message: error.message || 'Upload failed. Please try again.',
error: error
};
}
};};} };
## Customization Options
### Slot System
The GoalImageGallery supports a flexible slot system for customization:
#### Custom Image Selector
You can provide your own image selector component:
```javascript
// Custom selector component
const CustomImageSelector = ({
afterUpload,
activate,
onCancel,
multiple,
title,
uploadMessage,
maxFileSize,
acceptedFormats,
customStyles
}) => {
const handleFileSelect = async (files) => {
// Your custom upload logic
const result = await uploadFiles(files);
afterUpload(result);
};
return (
<div style={{ ...customStyles, padding: '20px' }}>
<h3>{title}</h3>
<p>{uploadMessage}</p>
<input
type="file"
multiple={multiple}
accept={acceptedFormats.join(',')}
onChange={(e) => handleFileSelect(e.target.files)}
/>
<button onClick={onCancel}>Cancel</button>
</div>
);
};
// Usage
<GoalImageGallery
// ... other props
slot={{
selector: CustomImageSelector
}}
slotProps={{
selector: {
maxFileSize: 10 * 1024 * 1024,
acceptedFormats: ['image/jpeg', 'image/png'],
customStyles: {
border: '2px dashed #007bff',
borderRadius: '8px',
backgroundColor: '#f8f9fa'
}
}
}}
/>Available Slots
| Slot | Description | Props Passed |
|------|-------------|--------------|
| selector | Custom image selector/uploader component | afterUpload, activate, onCancel, multiple, title, uploadMessage, and custom props from slotProps.selector |
Styling and Theming
The component uses Material-UI's theming system and can be customized:
const theme = createTheme({
components: {
MuiCard: {
styleOverrides: {
root: {
// Custom card styles
}
}
}
}
});Performance Considerations
- Image Optimization: Uses thumbnail versions for gallery view
- Lazy Loading: Images are loaded on demand
- Memory Management: Proper cleanup of object URLs
- Debounced Operations: Prevents excessive API calls
Error Handling
The component provides comprehensive error handling:
- Upload Errors: File size, format, or network issues
- Loading Errors: Broken image URLs or network problems
- Permission Errors: Insufficient user permissions
- Browser Compatibility: Graceful degradation for older browsers
Browser Support
- Modern Browsers: Full feature support
- Internet Explorer 11: Limited support (no clipboard paste)
- Mobile Browsers: Optimized experience
- Accessibility Tools: Full compatibility
Examples
Product Gallery
<GoalImageGallery
imageCodes={product.images}
canEdit={hasPermission('EDIT_PRODUCTS')}
ownerEntity={{ id: product.id, type: 'product' }}
imageHandlerApi={productImageApi.upload}
afterUpload={refreshProduct}
multiple={true}
emptyMessage="No product images available"
showImageInfo={true}
/>User Avatar
<GoalImageGallery
imageCodes={user.avatar}
canEdit={isOwner}
ownerEntity={{ id: user.id, type: 'user' }}
imageHandlerApi={userImageApi.upload}
afterUpload={refreshUser}
multiple={false}
emptyMessage="No avatar set"
showImageInfo={false}
/>Document Gallery
<GoalImageGallery
imageCodes={document.attachments}
canEdit={canEditDocument}
ownerEntity={{ id: document.id, type: 'document' }}
imageHandlerApi={documentImageApi.upload}
afterUpload={refreshDocument}
multiple={true}
permission="EDIT_DOCUMENTS"
allowDownload={true}
/>Best Practices
- Performance: Use thumbnail versions for gallery display
- Accessibility: Always provide meaningful alt text
- Error Handling: Implement comprehensive error handling
- Permissions: Check user permissions before enabling editing
- Validation: Validate file types and sizes on both client and server
- Responsive Design: Test on various screen sizes
- Loading States: Provide visual feedback during operations
Troubleshooting
Common Issues
Issue: Images not loading
- Solution: Check image URLs and permissions
Issue: Upload not working
- Solution: Verify
imageHandlerApiis properly configured
Issue: Clipboard paste not working
- Solution: Ensure HTTPS and modern browser support
Issue: Poor performance with many images
- Solution: Implement pagination or virtual scrolling
Debug Mode
Enable debug mode for development:
<GoalImageGallery
// ... other props
debug={process.env.NODE_ENV === 'development'}
/>Migration Guide
From CommonImageGallery
// Old
<CommonImageGallery
photos={photos}
canEdit={canEdit}
onSelectImage={handleSelect}
/>
// New
<GoalImageGallery
imageCodes={imageCodes}
canEdit={canEdit}
ownerEntity={ownerEntity}
imageHandlerApi={imageApi}
afterUpload={handleUpload}
/>From Basic Image Display
// Old
{images.map(img => (
<img key={img.id} src={img.url} alt={img.title} />
))}
// New
<GoalImageGallery
imageCodes={images.map(img => img.code)}
canEdit={false}
/>Contributing
When contributing to this component:
- Follow the existing code style
- Add tests for new features
- Update documentation
- Test accessibility features
- Verify mobile responsiveness
- Update CHANGELOG.md with your changes
Version History & Changelog
For detailed information about changes, new features, and updates across versions, see CHANGELOG.md.
Recent Updates
- v1.0.6: Major package cleanup and optimization
- v1.0.5: Initial package review and dependency analysis
- v1.0.4: Comprehensive documentation overhaul
- v1.0.3: NPM publication and scoped package setup
- v1.0.2: Slot system implementation and UI enhancements
- v1.0.1: Bug fixes and stability improvements
- v1.0.0: Initial release with core features
License
This component is part of the Goal application and follows the project's licensing terms.
🏗️ Modular Architecture
The component has been fully modularized for better maintainability and reusability:
Component Structure
goal-image-gallery/
├── index.js # Main exports
├── GoalImageGallery.js # Main component
├── components/ # Modular sub-components
│ ├── ImageGalleryGrid.js
│ ├── ImageGalleryModal.js
│ ├── ImageGalleryItem.js
│ └── ImageGalleryEmptyState.js
├── hooks/ # Custom hooks
│ └── use-image-gallery.js
├── styles/ # Styled components
│ └── styled-components.js
├── utils/ # Utility functions
│ └── image-utils.js
└── docs/ # Documentation
├── README.md
├── QUICK_REFERENCE.md
├── MIGRATION.md
└── examples.jsAvailable Exports
Main Component
import { GoalImageGallery } from '@mgonza02/goal-image-gallery';Sub-components
import {
ImageGalleryGrid,
ImageGalleryModal,
ImageGalleryItem,
ImageGalleryEmptyState
} from '@mgonza02/goal-image-gallery';Custom Hooks
import {
useImageGallery,
useImageZoom,
useClipboard,
useKeyboardNavigation
} from '@mgonza02/goal-image-gallery';Styled Components
import {
StyledImageContainer,
ImageOverlay,
StyledModalContainer
} from '@mgonza02/goal-image-gallery';Utility Functions
import {
handleImageError,
downloadImage,
processImageCodes,
getGridColumns,
isValidImageFile,
formatFileSize
} from '@mgonza02/goal-image-gallery';Using Individual Components
import {
ImageGalleryGrid,
ImageGalleryModal,
useImageGallery
} from '@mgonza02/goal-image-gallery';
function CustomGallery() {
const { imageList, openImage, handleOpenImage } = useImageGallery({
imageCodes: ['image1', 'image2'],
multiple: true
});
return (
<>
<ImageGalleryGrid
imageList={imageList}
onImageClick={handleOpenImage}
emptyMessage="No images"
/>
<ImageGalleryModal
open={openImage !== null}
imageUrl={openImage}
imageList={imageList}
onClose={() => handleOpenImage(null)}
/>
</>
);
}📦 Independent Package
The component is now ready for export as an independent package:
{
"name": "@mgonza02/goal-image-gallery",
"version": "1.0.0",
"description": "Enhanced, modular image gallery component for React applications",
"main": "lib/index.js",
"module": "lib/index.js",
"types": "lib/index.d.ts"
}TypeScript Support
Full TypeScript definitions are included:
interface GoalImageGalleryProps {
canEdit?: boolean;
ownerEntity?: object;
imageHandlerApi?: (data: any) => Promise<any>;
afterUpload?: (result: any) => void;
imageCodes?: string | string[];
multiple?: boolean;
permission?: string;
emptyMessage?: string;
showImageInfo?: boolean;
allowDownload?: boolean;
}