Gathering detailed insights and metrics for @mgonza02/goal-image-gallery
Gathering detailed insights and metrics for @mgonza02/goal-image-gallery
Gathering detailed insights and metrics for @mgonza02/goal-image-gallery
Gathering detailed insights and metrics for @mgonza02/goal-image-gallery
npm install @mgonza02/goal-image-gallery
Typescript
Module System
Min. Node Version
Node Version
NPM Version
JavaScript (98.62%)
Shell (0.97%)
Dockerfile (0.41%)
Total Downloads
0
Last Day
0
Last Week
0
Last Month
0
Last Year
0
MIT License
9 Commits
1 Branches
1 Contributors
Updated on Jul 11, 2025
Latest Version
1.0.6
Package Id
@mgonza02/goal-image-gallery@1.0.6
Unpacked Size
192.14 kB
Size
48.59 kB
File Count
22
NPM Version
10.9.2
Node Version
22.17.0
Published on
Jul 11, 2025
Cumulative downloads
Total Downloads
Last Day
0%
NaN
Compared to previous day
Last Week
0%
NaN
Compared to previous week
Last Month
0%
NaN
Compared to previous month
Last Year
0%
NaN
Compared to previous year
1
5
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.
1npm install @mgonza02/goal-image-gallery
1import { GoalImageGallery } from '@mgonza02/goal-image-gallery'; 2 3// Basic usage 4<GoalImageGallery imageCodes={['img1', 'img2']} canEdit={false} />
1npm install @mgonza02/goal-image-gallery
Make sure you have the required peer dependencies installed:
1npm install @mui/material @mui/icons-material @emotion/react @emotion/styled react react-dom
1// Main component 2import { GoalImageGallery } from '@mgonza02/goal-image-gallery'; 3 4// Individual components and hooks 5import { 6 ImageGalleryGrid, 7 ImageGalleryModal, 8 useImageGallery, 9 useImageZoom 10} from '@mgonza02/goal-image-gallery';
The component includes full TypeScript definitions:
1import { GoalImageGalleryProps } from '@mgonza02/goal-image-gallery'; 2 3const MyComponent: React.FC<{ images: string[] }> = ({ images }) => { 4 return ( 5 <GoalImageGallery 6 imageCodes={images} 7 canEdit={true} 8 // TypeScript will provide full autocomplete and type checking 9 /> 10 ); 11};
Perfect for displaying images without editing capabilities:
1<GoalImageGallery 2 imageCodes={['img1', 'img2', 'img3']} 3 canEdit={false} 4 emptyMessage="No images to display" 5 getImageUrl={({ imageCode }) => `https://api.example.com/images/${imageCode}`} 6/>
Full-featured gallery with upload, edit, and delete capabilities:
1const ProductGallery = () => { 2 const [productImages, setProductImages] = useState(['prod1', 'prod2']); 3 4 const handleImageUpload = async (imageData) => { 5 try { 6 const response = await api.uploadProductImage({ 7 ...imageData, 8 productId: product.id, 9 companyId: currentCompany 10 }); 11 12 return { 13 success: true, 14 data: response.data, 15 message: 'Images uploaded successfully' 16 }; 17 } catch (error) { 18 return { 19 success: false, 20 message: error.message 21 }; 22 } 23 }; 24 25 const refreshProduct = (result) => { 26 // Update product data 27 setProductImages(prev => [...prev, result.data.code]); 28 }; 29 30 return ( 31 <GoalImageGallery 32 imageCodes={productImages} 33 canEdit={true} 34 ownerEntity={{ id: product.id, type: 'product' }} 35 imageHandlerApi={handleImageUpload} 36 afterUpload={refreshProduct} 37 multiple={true} 38 emptyMessage="No product images available" 39 showImageInfo={true} 40 allowDownload={true} 41 permission="EDIT_PRODUCTS" 42 hasPermission={(perm) => userPermissions.includes(perm)} 43 currentCompany={user.companyId} 44 /> 45 ); 46};
Single image gallery for user avatars:
1<GoalImageGallery 2 imageCodes={user.avatar} 3 canEdit={isOwner} 4 ownerEntity={{ id: user.id, type: 'user' }} 5 imageHandlerApi={userImageApi.upload} 6 afterUpload={refreshUser} 7 multiple={false} 8 emptyMessage="No avatar set" 9 showImageInfo={false} 10 getImageUrl={({ imageCode }) => `https://cdn.example.com/avatars/${imageCode}`} 11/>
Using the slot system to provide a custom image selector:
1// Custom selector component 2const CustomImageSelector = ({ 3 afterUpload, 4 activate, 5 onCancel, 6 multiple, 7 title, 8 uploadMessage, 9 maxFileSize, 10 acceptedFormats, 11 customStyles 12}) => { 13 const [dragActive, setDragActive] = useState(false); 14 const [uploading, setUploading] = useState(false); 15 16 const handleFiles = async (files) => { 17 setUploading(true); 18 try { 19 // Custom upload logic 20 const result = await uploadFiles(files); 21 afterUpload(result); 22 } catch (error) { 23 console.error('Upload failed:', error); 24 } finally { 25 setUploading(false); 26 } 27 }; 28 29 return ( 30 <div 31 style={{ 32 ...customStyles, 33 border: dragActive ? '2px solid #007bff' : '2px dashed #ccc', 34 borderRadius: '8px', 35 padding: '40px', 36 textAlign: 'center', 37 backgroundColor: dragActive ? '#f8f9fa' : '#fff' 38 }} 39 onDragOver={(e) => { 40 e.preventDefault(); 41 setDragActive(true); 42 }} 43 onDragLeave={() => setDragActive(false)} 44 onDrop={async (e) => { 45 e.preventDefault(); 46 setDragActive(false); 47 const files = Array.from(e.dataTransfer.files); 48 await handleFiles(files); 49 }} 50 > 51 <h3>{title}</h3> 52 <p>{uploadMessage}</p> 53 {uploading && <p>Uploading...</p>} 54 <input 55 type="file" 56 multiple={multiple} 57 accept={acceptedFormats?.join(',')} 58 onChange={(e) => handleFiles(Array.from(e.target.files))} 59 style={{ margin: '10px 0' }} 60 /> 61 <br /> 62 <button onClick={onCancel} disabled={uploading}> 63 Cancel 64 </button> 65 </div> 66 ); 67}; 68 69// Usage with custom selector 70<GoalImageGallery 71 imageCodes={images} 72 canEdit={true} 73 ownerEntity={{ id: 'entity-123' }} 74 imageHandlerApi={handleUpload} 75 afterUpload={refreshData} 76 multiple={true} 77 slot={{ 78 selector: CustomImageSelector 79 }} 80 slotProps={{ 81 selector: { 82 maxFileSize: 10 * 1024 * 1024, // 10MB 83 acceptedFormats: ['image/jpeg', 'image/png', 'image/webp'], 84 customStyles: { 85 borderColor: '#007bff', 86 backgroundColor: '#f8f9fa' 87 } 88 } 89 }} 90/>
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}
/>
The most common method for uploading images:
1// Features: 2// - Click to select files 3// - Drag and drop support 4// - Multiple file selection 5// - File type validation 6// - Size limit enforcement 7// - Progress indication 8 9<GoalImageGallery 10 canEdit={true} 11 multiple={true} 12 maxFileSize={10 * 1024 * 1024} // 10MB 13 acceptedFormats={['image/jpeg', 'image/png', 'image/webp']} 14 // ... other props 15/>
Supported Features:
Revolutionary clipboard image support:
1// Usage: 2// 1. Copy image from any source (screenshot, browser, etc.) 3// 2. Focus on the gallery 4// 3. Press Ctrl+V (Cmd+V on Mac) 5// 4. Image is automatically processed and uploaded 6 7// Auto-enabled when canEdit={true} 8<GoalImageGallery 9 canEdit={true} 10 // Clipboard paste is automatically available 11/>
Clipboard Features:
Load images from external URLs:
1// Users can enter image URLs directly 2// Component validates and loads the image 3// Supports CORS-enabled images 4 5const handleUrlUpload = async (imageUrl) => { 6 try { 7 const response = await fetch(imageUrl); 8 const blob = await response.blob(); 9 10 // Process the image 11 const result = await processImageBlob(blob); 12 13 return { 14 success: true, 15 data: result 16 }; 17 } catch (error) { 18 return { 19 success: false, 20 message: 'Failed to load image from URL' 21 }; 22 } 23};
Enhanced mobile support:
1// Mobile-specific features: 2// - Camera access 3// - Photo library access 4// - Touch gestures 5// - Responsive design 6 7<GoalImageGallery 8 canEdit={true} 9 // Mobile camera is automatically available on mobile devices 10 mobileOptimized={true} 11 touchGestures={true} 12/>
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 |
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 |
1// Built-in accessibility features: 2<GoalImageGallery 3 // ARIA labels are automatically generated 4 ariaLabel="Product image gallery" 5 ariaDescribedBy="gallery-instructions" 6 7 // Screen reader support 8 screenReaderOptimized={true} 9 10 // High contrast mode 11 highContrastMode={true} 12 13 // Keyboard navigation 14 keyboardNavigation={true} 15 16 // Focus management 17 manageFocus={true} 18/>
1import { useKeyboardNavigation } from '@mgonza02/goal-image-gallery'; 2 3const CustomGallery = () => { 4 const { handleKeyPress } = useKeyboardNavigation({ 5 onPreviousImage: () => console.log('Previous'), 6 onNextImage: () => console.log('Next'), 7 onCloseImage: () => console.log('Close'), 8 onZoomIn: () => console.log('Zoom in'), 9 onZoomOut: () => console.log('Zoom out'), 10 // Add custom shortcuts 11 customShortcuts: { 12 'd': () => downloadCurrentImage(), 13 'i': () => showImageInfo(), 14 'f': () => toggleFullscreen() 15 } 16 }); 17 18 return ( 19 <div onKeyDown={handleKeyPress}> 20 <GoalImageGallery {...props} /> 21 </div> 22 ); 23};
The imageHandlerApi
function is the core of the upload system:
1const handleImageUpload = async (imageData) => { 2 try { 3 // imageData contains: 4 // - files: Array of File objects 5 // - metadata: Additional data (owner, company, etc.) 6 // - options: Upload options (compression, format, etc.) 7 8 const response = await api.uploadImages({ 9 files: imageData.files, 10 companyId: imageData.metadata.companyId, 11 entityId: imageData.metadata.entityId, 12 entityType: imageData.metadata.entityType, 13 folder: imageData.options.folder, 14 compression: imageData.options.compression, 15 thumbnailSize: imageData.options.thumbnailSize 16 }); 17 18 // Must return standardized response 19 return { 20 success: true, 21 data: response.data, // Array of uploaded image objects 22 message: 'Images uploaded successfully', 23 codes: response.data.map(img => img.code) // Optional: image codes 24 }; 25 } catch (error) { 26 return { 27 success: false, 28 message: error.message || 'Upload failed', 29 error: error 30 }; 31 } 32};
Expected response format from your API:
1// Successful response 2{ 3 success: true, 4 data: [ 5 { 6 id: 123, 7 code: 'img_abc123', 8 url: 'https://cdn.example.com/images/img_abc123.jpg', 9 thumbnailUrl: 'https://cdn.example.com/images/img_abc123_thumb.jpg', 10 filename: 'product-image.jpg', 11 size: 1024000, 12 width: 1920, 13 height: 1080, 14 mimeType: 'image/jpeg', 15 uploadedAt: '2023-01-01T00:00:00Z' 16 } 17 ], 18 message: 'Upload successful' 19} 20 21// Error response 22{ 23 success: false, 24 message: 'File size too large', 25 error: { 26 code: 'FILE_TOO_LARGE', 27 details: 'Maximum file size is 25MB' 28 } 29}
1const restApiHandler = async (imageData) => { 2 const formData = new FormData(); 3 4 // Add files 5 imageData.files.forEach((file, index) => { 6 formData.append(`files[${index}]`, file); 7 }); 8 9 // Add metadata 10 formData.append('companyId', imageData.metadata.companyId); 11 formData.append('entityId', imageData.metadata.entityId); 12 formData.append('entityType', imageData.metadata.entityType); 13 14 try { 15 const response = await fetch('/api/images/upload', { 16 method: 'POST', 17 body: formData, 18 headers: { 19 'Authorization': `Bearer ${authToken}`, 20 // Don't set Content-Type, let browser set it with boundary 21 } 22 }); 23 24 const result = await response.json(); 25 26 if (!response.ok) { 27 throw new Error(result.message || 'Upload failed'); 28 } 29 30 return { 31 success: true, 32 data: result.images, 33 message: result.message 34 }; 35 } catch (error) { 36 return { 37 success: false, 38 message: error.message 39 }; 40 } 41};
1import { useMutation } from '@apollo/client'; 2 3const UPLOAD_IMAGES = gql` 4 mutation UploadImages($files: [Upload!]!, $input: ImageUploadInput!) { 5 uploadImages(files: $files, input: $input) { 6 success 7 message 8 images { 9 id 10 code 11 url 12 thumbnailUrl 13 filename 14 size 15 width 16 height 17 } 18 } 19 } 20`; 21 22const GraphQLImageGallery = ({ ownerEntity, ...props }) => { 23 const [uploadImages] = useMutation(UPLOAD_IMAGES); 24 25 const handleImageUpload = async (imageData) => { 26 try { 27 const { data } = await uploadImages({ 28 variables: { 29 files: imageData.files, 30 input: { 31 companyId: imageData.metadata.companyId, 32 entityId: imageData.metadata.entityId, 33 entityType: imageData.metadata.entityType 34 } 35 } 36 }); 37 38 return { 39 success: data.uploadImages.success, 40 data: data.uploadImages.images, 41 message: data.uploadImages.message 42 }; 43 } catch (error) { 44 return { 45 success: false, 46 message: error.message 47 }; 48 } 49 }; 50 51 return ( 52 <GoalImageGallery 53 {...props} 54 ownerEntity={ownerEntity} 55 imageHandlerApi={handleImageUpload} 56 /> 57 ); 58};
1import { ref, uploadBytes, getDownloadURL } from 'firebase/storage'; 2import { storage } from './firebaseConfig'; 3 4const firebaseHandler = async (imageData) => { 5 try { 6 const uploadPromises = imageData.files.map(async (file) => { 7 const storageRef = ref(storage, `images/${Date.now()}_${file.name}`); 8 const snapshot = await uploadBytes(storageRef, file); 9 const downloadURL = await getDownloadURL(snapshot.ref); 10 11 return { 12 id: snapshot.ref.fullPath, 13 code: snapshot.ref.name, 14 url: downloadURL, 15 filename: file.name, 16 size: file.size, 17 mimeType: file.type 18 }; 19 }); 20 21 const results = await Promise.all(uploadPromises); 22 23 return { 24 success: true, 25 data: results, 26 message: 'Images uploaded to Firebase' 27 }; 28 } catch (error) { 29 return { 30 success: false, 31 message: error.message 32 }; 33 } 34};
1const robustApiHandler = async (imageData) => { 2 try { 3 // Validate files before upload 4 const validFiles = imageData.files.filter(file => { 5 if (file.size > 25 * 1024 * 1024) { 6 console.warn(`File ${file.name} is too large`); 7 return false; 8 } 9 if (!file.type.startsWith('image/')) { 10 console.warn(`File ${file.name} is not an image`); 11 return false; 12 } 13 return true; 14 }); 15 16 if (validFiles.length === 0) { 17 return { 18 success: false, 19 message: 'No valid files to upload' 20 }; 21 } 22 23 // Attempt upload with retry logic 24 let attempts = 0; 25 const maxAttempts = 3; 26 27 while (attempts < maxAttempts) { 28 try { 29 const response = await uploadFiles(validFiles); 30 return { 31 success: true, 32 data: response.data, 33 message: 'Upload successful' 34 }; 35 } catch (error) { 36 attempts++; 37 if (attempts >= maxAttempts) { 38 throw error; 39 } 40 41 // Wait before retry 42 await new Promise(resolve => setTimeout(resolve, 1000 * attempts)); 43 } 44 } 45 } catch (error) { 46 // Log error for debugging 47 console.error('Image upload failed:', error); 48 49 return { 50 success: false, 51 message: error.message || 'Upload failed. Please try again.', 52 error: error 53 }; 54 } 55};
};
} };
## 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'
}
}
}}
/>
Slot | Description | Props Passed |
---|---|---|
selector | Custom image selector/uploader component | afterUpload , activate , onCancel , multiple , title , uploadMessage , and custom props from slotProps.selector |
The component uses Material-UI's theming system and can be customized:
1const theme = createTheme({ 2 components: { 3 MuiCard: { 4 styleOverrides: { 5 root: { 6 // Custom card styles 7 } 8 } 9 } 10 } 11});
The component provides comprehensive error handling:
1<GoalImageGallery 2 imageCodes={product.images} 3 canEdit={hasPermission('EDIT_PRODUCTS')} 4 ownerEntity={{ id: product.id, type: 'product' }} 5 imageHandlerApi={productImageApi.upload} 6 afterUpload={refreshProduct} 7 multiple={true} 8 emptyMessage="No product images available" 9 showImageInfo={true} 10/>
1<GoalImageGallery 2 imageCodes={user.avatar} 3 canEdit={isOwner} 4 ownerEntity={{ id: user.id, type: 'user' }} 5 imageHandlerApi={userImageApi.upload} 6 afterUpload={refreshUser} 7 multiple={false} 8 emptyMessage="No avatar set" 9 showImageInfo={false} 10/>
1<GoalImageGallery 2 imageCodes={document.attachments} 3 canEdit={canEditDocument} 4 ownerEntity={{ id: document.id, type: 'document' }} 5 imageHandlerApi={documentImageApi.upload} 6 afterUpload={refreshDocument} 7 multiple={true} 8 permission="EDIT_DOCUMENTS" 9 allowDownload={true} 10/>
Issue: Images not loading
Issue: Upload not working
imageHandlerApi
is properly configuredIssue: Clipboard paste not working
Issue: Poor performance with many images
Enable debug mode for development:
1<GoalImageGallery 2 // ... other props 3 debug={process.env.NODE_ENV === 'development'} 4/>
1// Old 2<CommonImageGallery 3 photos={photos} 4 canEdit={canEdit} 5 onSelectImage={handleSelect} 6/> 7 8// New 9<GoalImageGallery 10 imageCodes={imageCodes} 11 canEdit={canEdit} 12 ownerEntity={ownerEntity} 13 imageHandlerApi={imageApi} 14 afterUpload={handleUpload} 15/>
1// Old 2{images.map(img => ( 3 <img key={img.id} src={img.url} alt={img.title} /> 4))} 5 6// New 7<GoalImageGallery 8 imageCodes={images.map(img => img.code)} 9 canEdit={false} 10/>
When contributing to this component:
For detailed information about changes, new features, and updates across versions, see CHANGELOG.md.
This component is part of the Goal application and follows the project's licensing terms.
The component has been fully modularized for better maintainability and reusability:
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.js
1import { GoalImageGallery } from '@mgonza02/goal-image-gallery';
1import { 2 ImageGalleryGrid, 3 ImageGalleryModal, 4 ImageGalleryItem, 5 ImageGalleryEmptyState 6} from '@mgonza02/goal-image-gallery';
1import { 2 useImageGallery, 3 useImageZoom, 4 useClipboard, 5 useKeyboardNavigation 6} from '@mgonza02/goal-image-gallery';
1import { 2 StyledImageContainer, 3 ImageOverlay, 4 StyledModalContainer 5} from '@mgonza02/goal-image-gallery';
1import { 2 handleImageError, 3 downloadImage, 4 processImageCodes, 5 getGridColumns, 6 isValidImageFile, 7 formatFileSize 8} from '@mgonza02/goal-image-gallery';
1import { 2 ImageGalleryGrid, 3 ImageGalleryModal, 4 useImageGallery 5} from '@mgonza02/goal-image-gallery'; 6 7function CustomGallery() { 8 const { imageList, openImage, handleOpenImage } = useImageGallery({ 9 imageCodes: ['image1', 'image2'], 10 multiple: true 11 }); 12 13 return ( 14 <> 15 <ImageGalleryGrid 16 imageList={imageList} 17 onImageClick={handleOpenImage} 18 emptyMessage="No images" 19 /> 20 <ImageGalleryModal 21 open={openImage !== null} 22 imageUrl={openImage} 23 imageList={imageList} 24 onClose={() => handleOpenImage(null)} 25 /> 26 </> 27 ); 28}
The component is now ready for export as an independent package:
1{ 2 "name": "@mgonza02/goal-image-gallery", 3 "version": "1.0.0", 4 "description": "Enhanced, modular image gallery component for React applications", 5 "main": "lib/index.js", 6 "module": "lib/index.js", 7 "types": "lib/index.d.ts" 8}
Full TypeScript definitions are included:
1interface GoalImageGalleryProps { 2 canEdit?: boolean; 3 ownerEntity?: object; 4 imageHandlerApi?: (data: any) => Promise<any>; 5 afterUpload?: (result: any) => void; 6 imageCodes?: string | string[]; 7 multiple?: boolean; 8 permission?: string; 9 emptyMessage?: string; 10 showImageInfo?: boolean; 11 allowDownload?: boolean; 12}
No vulnerabilities found.
No security vulnerabilities found.