@mgonza02/goal-image-gallery 1.0.2 → 1.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,7 +2,20 @@
2
2
 
3
3
  ## Overview
4
4
 
5
- The `GoalImageGallery` is a modern, feature-rich image gallery component for the Goal application. It provides a comprehensive solution for displaying, managing, and uploading images with advanced UI/UX features and accessibility support.
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.
6
+
7
+ ## 🚀 Quick Start
8
+
9
+ ```bash
10
+ npm install @mgonza02/goal-image-gallery
11
+ ```
12
+
13
+ ```javascript
14
+ import { GoalImageGallery } from '@mgonza02/goal-image-gallery';
15
+
16
+ // Basic usage
17
+ <GoalImageGallery imageCodes={['img1', 'img2']} canEdit={false} />
18
+ ```
6
19
 
7
20
  ## Key Features
8
21
 
@@ -10,152 +23,770 @@ The `GoalImageGallery` is a modern, feature-rich image gallery component for the
10
23
  - **File Upload**: Traditional file selection with drag-and-drop support
11
24
  - **Clipboard Paste**: Paste images directly from clipboard using Ctrl+V
12
25
  - **URL Loading**: Load images from remote URLs
26
+ - **Batch Upload**: Upload multiple images simultaneously
13
27
 
14
28
  ### 🎨 **Modern UI/UX**
15
- - Responsive grid layout that adapts to screen size
16
- - Smooth hover effects and transitions
17
- - Material Design 3 styling
18
- - Dark/light theme support
19
- - Loading states and skeleton animations
29
+ - **Responsive Design**: Adapts to all screen sizes and devices
30
+ - **Material Design 3**: Latest Material-UI components and styling
31
+ - **Smooth Animations**: Hover effects, transitions, and loading states
32
+ - **Dark/Light Theme**: Automatic theme detection and support
33
+ - **Accessibility**: WCAG 2.1 AA compliant with full keyboard navigation
20
34
 
21
35
  ### 🔍 **Advanced Image Viewer**
22
- - Full-screen modal viewer
23
- - Zoom controls (0.5x to 3x)
24
- - Image navigation (previous/next)
25
- - Keyboard navigation support
26
- - Download functionality
27
-
28
- ### ♿ **Accessibility**
29
- - ARIA labels and roles
30
- - Keyboard navigation
31
- - Screen reader support
32
- - High contrast mode compatibility
33
- - Focus management
34
-
35
- ### 📱 **Mobile Responsive**
36
- - Touch-friendly interface
37
- - Optimized for mobile devices
38
- - Swipe gestures support
39
- - Responsive breakpoints
40
-
41
- ## Installation
36
+ - **Full-Screen Modal**: Immersive image viewing experience
37
+ - **Zoom Controls**: Zoom from 0.5x to 3x with smooth transitions
38
+ - **Image Navigation**: Previous/next with keyboard shortcuts
39
+ - **Download Support**: Direct image download functionality
40
+ - **Image Information**: Display metadata and details
41
+
42
+ ### 🛠️ **Customization & Extensibility**
43
+ - **Slot System**: Replace default components with custom implementations
44
+ - **Flexible Props**: Extensive configuration options
45
+ - **TypeScript Support**: Full type definitions included
46
+ - **Hook-Based Architecture**: Reusable custom hooks for complex logic
47
+
48
+ ### 📱 **Mobile-First Design**
49
+ - **Touch Gestures**: Swipe, pinch-to-zoom, and tap interactions
50
+ - **Responsive Breakpoints**: Optimized layouts for all devices
51
+ - **Performance Optimized**: Lazy loading and efficient rendering
52
+ - **Offline Support**: Graceful degradation for poor connectivity
53
+
54
+ ## Installation & Setup
55
+
56
+ ### NPM Installation
42
57
 
43
58
  ```bash
44
59
  npm install @mgonza02/goal-image-gallery
45
60
  ```
46
61
 
62
+ ### Peer Dependencies
63
+
64
+ Make sure you have the required peer dependencies installed:
65
+
66
+ ```bash
67
+ npm install @mui/material @mui/icons-material @emotion/react @emotion/styled react react-dom
68
+ ```
69
+
70
+ ### Import
71
+
47
72
  ```javascript
73
+ // Main component
48
74
  import { GoalImageGallery } from '@mgonza02/goal-image-gallery';
75
+
76
+ // Individual components and hooks
77
+ import {
78
+ ImageGalleryGrid,
79
+ ImageGalleryModal,
80
+ useImageGallery,
81
+ useImageZoom
82
+ } from '@mgonza02/goal-image-gallery';
83
+ ```
84
+
85
+ ### TypeScript Support
86
+
87
+ The component includes full TypeScript definitions:
88
+
89
+ ```typescript
90
+ import { GoalImageGalleryProps } from '@mgonza02/goal-image-gallery';
91
+
92
+ const MyComponent: React.FC<{ images: string[] }> = ({ images }) => {
93
+ return (
94
+ <GoalImageGallery
95
+ imageCodes={images}
96
+ canEdit={true}
97
+ // TypeScript will provide full autocomplete and type checking
98
+ />
99
+ );
100
+ };
49
101
  ```
50
102
 
51
- ## Basic Usage
103
+ ## Usage Examples
104
+
105
+ ### 1. Basic Read-Only Gallery
106
+
107
+ Perfect for displaying images without editing capabilities:
52
108
 
53
- ### Simple Gallery
54
109
  ```javascript
55
110
  <GoalImageGallery
56
111
  imageCodes={['img1', 'img2', 'img3']}
57
112
  canEdit={false}
113
+ emptyMessage="No images to display"
114
+ getImageUrl={({ imageCode }) => `https://api.example.com/images/${imageCode}`}
58
115
  />
59
116
  ```
60
117
 
61
- ### Editable Gallery
118
+ ### 2. Editable Product Gallery
119
+
120
+ Full-featured gallery with upload, edit, and delete capabilities:
121
+
122
+ ```javascript
123
+ const ProductGallery = () => {
124
+ const [productImages, setProductImages] = useState(['prod1', 'prod2']);
125
+
126
+ const handleImageUpload = async (imageData) => {
127
+ try {
128
+ const response = await api.uploadProductImage({
129
+ ...imageData,
130
+ productId: product.id,
131
+ companyId: currentCompany
132
+ });
133
+
134
+ return {
135
+ success: true,
136
+ data: response.data,
137
+ message: 'Images uploaded successfully'
138
+ };
139
+ } catch (error) {
140
+ return {
141
+ success: false,
142
+ message: error.message
143
+ };
144
+ }
145
+ };
146
+
147
+ const refreshProduct = (result) => {
148
+ // Update product data
149
+ setProductImages(prev => [...prev, result.data.code]);
150
+ };
151
+
152
+ return (
153
+ <GoalImageGallery
154
+ imageCodes={productImages}
155
+ canEdit={true}
156
+ ownerEntity={{ id: product.id, type: 'product' }}
157
+ imageHandlerApi={handleImageUpload}
158
+ afterUpload={refreshProduct}
159
+ multiple={true}
160
+ emptyMessage="No product images available"
161
+ showImageInfo={true}
162
+ allowDownload={true}
163
+ permission="EDIT_PRODUCTS"
164
+ hasPermission={(perm) => userPermissions.includes(perm)}
165
+ currentCompany={user.companyId}
166
+ />
167
+ );
168
+ };
169
+ ```
170
+
171
+ ### 3. User Avatar Gallery
172
+
173
+ Single image gallery for user avatars:
174
+
62
175
  ```javascript
63
176
  <GoalImageGallery
64
- imageCodes={imageCodes}
65
- canEdit={true}
66
- ownerEntity={{ id: 'product-123' }}
67
- imageHandlerApi={handleImageUpload}
68
- afterUpload={refreshImages}
69
- multiple={true}
177
+ imageCodes={user.avatar}
178
+ canEdit={isOwner}
179
+ ownerEntity={{ id: user.id, type: 'user' }}
180
+ imageHandlerApi={userImageApi.upload}
181
+ afterUpload={refreshUser}
182
+ multiple={false}
183
+ emptyMessage="No avatar set"
184
+ showImageInfo={false}
185
+ getImageUrl={({ imageCode }) => `https://cdn.example.com/avatars/${imageCode}`}
70
186
  />
71
187
  ```
72
188
 
73
- ### Advanced Configuration
189
+ ### 4. Custom Image Selector
190
+
191
+ Using the slot system to provide a custom image selector:
192
+
74
193
  ```javascript
194
+ // Custom selector component
195
+ const CustomImageSelector = ({
196
+ afterUpload,
197
+ activate,
198
+ onCancel,
199
+ multiple,
200
+ title,
201
+ uploadMessage,
202
+ maxFileSize,
203
+ acceptedFormats,
204
+ customStyles
205
+ }) => {
206
+ const [dragActive, setDragActive] = useState(false);
207
+ const [uploading, setUploading] = useState(false);
208
+
209
+ const handleFiles = async (files) => {
210
+ setUploading(true);
211
+ try {
212
+ // Custom upload logic
213
+ const result = await uploadFiles(files);
214
+ afterUpload(result);
215
+ } catch (error) {
216
+ console.error('Upload failed:', error);
217
+ } finally {
218
+ setUploading(false);
219
+ }
220
+ };
221
+
222
+ return (
223
+ <div
224
+ style={{
225
+ ...customStyles,
226
+ border: dragActive ? '2px solid #007bff' : '2px dashed #ccc',
227
+ borderRadius: '8px',
228
+ padding: '40px',
229
+ textAlign: 'center',
230
+ backgroundColor: dragActive ? '#f8f9fa' : '#fff'
231
+ }}
232
+ onDragOver={(e) => {
233
+ e.preventDefault();
234
+ setDragActive(true);
235
+ }}
236
+ onDragLeave={() => setDragActive(false)}
237
+ onDrop={async (e) => {
238
+ e.preventDefault();
239
+ setDragActive(false);
240
+ const files = Array.from(e.dataTransfer.files);
241
+ await handleFiles(files);
242
+ }}
243
+ >
244
+ <h3>{title}</h3>
245
+ <p>{uploadMessage}</p>
246
+ {uploading && <p>Uploading...</p>}
247
+ <input
248
+ type="file"
249
+ multiple={multiple}
250
+ accept={acceptedFormats?.join(',')}
251
+ onChange={(e) => handleFiles(Array.from(e.target.files))}
252
+ style={{ margin: '10px 0' }}
253
+ />
254
+ <br />
255
+ <button onClick={onCancel} disabled={uploading}>
256
+ Cancel
257
+ </button>
258
+ </div>
259
+ );
260
+ };
261
+
262
+ // Usage with custom selector
75
263
  <GoalImageGallery
76
- imageCodes={imageCodes}
264
+ imageCodes={images}
77
265
  canEdit={true}
266
+ ownerEntity={{ id: 'entity-123' }}
267
+ imageHandlerApi={handleUpload}
268
+ afterUpload={refreshData}
269
+ multiple={true}
270
+ slot={{
271
+ selector: CustomImageSelector
272
+ }}
273
+ slotProps={{
274
+ selector: {
275
+ maxFileSize: 10 * 1024 * 1024, // 10MB
276
+ acceptedFormats: ['image/jpeg', 'image/png', 'image/webp'],
277
+ customStyles: {
278
+ borderColor: '#007bff',
279
+ backgroundColor: '#f8f9fa'
280
+ }
281
+ }
282
+ }}
283
+ />
284
+ ```
78
285
  ownerEntity={{ id: 'product-123', type: 'product' }}
79
286
  imageHandlerApi={handleImageUpload}
80
287
  afterUpload={refreshImages}
81
288
  multiple={true}
82
- permission="EDIT_IMAGES"
83
- emptyMessage="No product images available"
289
+ slot={{
290
+ selector: CustomImageSelector
291
+ }}
292
+ slotProps={{
293
+ selector: {
294
+ maxFileSize: 10 * 1024 * 1024, // 10MB
295
+ acceptedFormats: ['image/jpeg', 'image/png'],
296
+ uploadEndpoint: '/api/upload',
297
+ customStyles: { borderColor: '#007bff' }
298
+ }
299
+ }}
300
+ />
301
+ ```
302
+
303
+ ## Props Reference
304
+
305
+ ### Core Props
306
+
307
+ | Prop | Type | Default | Required | Description |
308
+ |------|------|---------|----------|-------------|
309
+ | `imageCodes` | `string \| string[]` | `[]` | No | Image codes to display in the gallery |
310
+ | `canEdit` | `boolean` | `false` | No | Enable editing capabilities (upload, delete) |
311
+ | `ownerEntity` | `object` | `undefined` | No | Entity that owns the images (required for editing) |
312
+ | `imageHandlerApi` | `function` | `undefined` | No | API function for image operations (required for editing) |
313
+ | `afterUpload` | `function` | `undefined` | No | Callback function called after successful upload |
314
+ | `multiple` | `boolean` | `false` | No | Allow multiple image selection and upload |
315
+
316
+ ### Display Props
317
+
318
+ | Prop | Type | Default | Required | Description |
319
+ |------|------|---------|----------|-------------|
320
+ | `emptyMessage` | `string` | `'No hay imágenes para mostrar'` | No | Message displayed when no images are available |
321
+ | `showImageInfo` | `boolean` | `false` | No | Show image information overlay on hover |
322
+ | `allowDownload` | `boolean` | `false` | No | Enable image download functionality |
323
+ | `getImageUrl` | `function` | `({ imageCode }) => '/images/${imageCode}'` | No | Function to generate image URLs from codes |
324
+ | `noImageUrl` | `string` | `'/images/no-image.png'` | No | Fallback image URL for broken images |
325
+
326
+ ### Permission Props
327
+
328
+ | Prop | Type | Default | Required | Description |
329
+ |------|------|---------|----------|-------------|
330
+ | `permission` | `string` | `undefined` | No | Required permission string for editing |
331
+ | `hasPermission` | `function` | `() => true` | No | Function to check if user has required permission |
332
+ | `currentCompany` | `string` | `'default'` | No | Current company identifier for API calls |
333
+
334
+ ### Customization Props
335
+
336
+ | Prop | Type | Default | Required | Description |
337
+ |------|------|---------|----------|-------------|
338
+ | `slot` | `object` | `{}` | No | Slot components for customization |
339
+ | `slotProps` | `object` | `{}` | No | Props passed to slot components |
340
+ | `showError` | `function` | `console.error` | No | Function to display error messages |
341
+
342
+ ### Advanced Props
343
+
344
+ | Prop | Type | Default | Required | Description |
345
+ |------|------|---------|----------|-------------|
346
+ | `maxFileSize` | `number` | `25 * 1024 * 1024` | No | Maximum file size in bytes (25MB default) |
347
+ | `acceptedFormats` | `string[]` | `['image/jpeg', 'image/png', 'image/gif', 'image/webp']` | No | Accepted image formats |
348
+ | `thumbnailSize` | `number` | `200` | No | Thumbnail size in pixels |
349
+ | `gridColumns` | `number` | `auto` | No | Number of columns in grid (auto-calculated if not provided) |
350
+ | `enableLazyLoading` | `boolean` | `true` | No | Enable lazy loading for images |
351
+ | `enableVirtualization` | `boolean` | `false` | No | Enable virtual scrolling for large image sets |
352
+
353
+ ### Prop Examples
354
+
355
+ ```javascript
356
+ // Basic configuration
357
+ <GoalImageGallery
358
+ imageCodes={['img1', 'img2', 'img3']}
359
+ canEdit={false}
360
+ />
361
+
362
+ // Full configuration
363
+ <GoalImageGallery
364
+ imageCodes={product.images}
365
+ canEdit={user.canEdit}
366
+ ownerEntity={{ id: product.id, type: 'product' }}
367
+ imageHandlerApi={async (data) => {
368
+ const response = await api.uploadImage(data);
369
+ return { success: true, data: response };
370
+ }}
371
+ afterUpload={(result) => {
372
+ console.log('Upload successful:', result);
373
+ refreshProduct();
374
+ }}
375
+ multiple={true}
376
+ permission="EDIT_PRODUCTS"
377
+ emptyMessage="No product images yet"
84
378
  showImageInfo={true}
85
379
  allowDownload={true}
380
+ getImageUrl={({ imageCode, thumbMail }) =>
381
+ `https://api.example.com/images/${imageCode}${thumbMail ? '?thumb=true' : ''}`
382
+ }
383
+ noImageUrl="https://api.example.com/images/placeholder.png"
384
+ hasPermission={(perm) => user.permissions.includes(perm)}
385
+ currentCompany={user.companyId}
386
+ showError={(msg) => toast.error(msg)}
387
+ maxFileSize={10 * 1024 * 1024}
388
+ acceptedFormats={['image/jpeg', 'image/png']}
389
+ thumbnailSize={150}
390
+ gridColumns={3}
86
391
  />
87
392
  ```
88
393
 
89
- ## Props
394
+ ## Upload Methods & Features
395
+
396
+ ### 1. File Upload (Drag & Drop)
90
397
 
91
- | Prop | Type | Default | Description |
92
- |------|------|---------|-------------|
93
- | `canEdit` | `boolean` | `false` | Enable editing capabilities |
94
- | `ownerEntity` | `object` | - | Entity that owns the images |
95
- | `imageHandlerApi` | `function` | - | API function for image operations |
96
- | `afterUpload` | `function` | - | Callback after successful upload |
97
- | `imageCodes` | `string \| array` | - | Image codes to display |
98
- | `multiple` | `boolean` | `false` | Allow multiple image selection |
99
- | `permission` | `string` | - | Required permission for editing |
100
- | `emptyMessage` | `string` | `'No hay imágenes para mostrar'` | Message shown when no images |
101
- | `showImageInfo` | `boolean` | `false` | Show image information overlay |
102
- | `allowDownload` | `boolean` | `true` | Enable download functionality |
398
+ The most common method for uploading images:
103
399
 
104
- ## Upload Methods
400
+ ```javascript
401
+ // Features:
402
+ // - Click to select files
403
+ // - Drag and drop support
404
+ // - Multiple file selection
405
+ // - File type validation
406
+ // - Size limit enforcement
407
+ // - Progress indication
105
408
 
106
- ### 1. File Upload
107
- Users can upload images by:
108
- - Clicking the "Add Image" button
109
- - Dragging and dropping files onto the gallery
110
- - Using the file selection dialog
409
+ <GoalImageGallery
410
+ canEdit={true}
411
+ multiple={true}
412
+ maxFileSize={10 * 1024 * 1024} // 10MB
413
+ acceptedFormats={['image/jpeg', 'image/png', 'image/webp']}
414
+ // ... other props
415
+ />
416
+ ```
111
417
 
112
- **Supported formats**: JPEG, PNG, GIF, WebP
113
- **Maximum file size**: 25MB (configurable)
418
+ **Supported Features:**
419
+ - **File Types**: JPEG, PNG, GIF, WebP, SVG
420
+ - **Size Limits**: Configurable (default: 25MB)
421
+ - **Batch Upload**: Upload multiple files simultaneously
422
+ - **Progress Tracking**: Visual progress indicators
423
+ - **Error Handling**: File validation and error reporting
114
424
 
115
425
  ### 2. Clipboard Paste
116
- Users can paste images directly from the clipboard:
117
- - Copy an image from any source (screenshot, browser, etc.)
118
- - Press `Ctrl+V` anywhere in the gallery
119
- - The image will be automatically processed and uploaded
120
426
 
121
- **Note**: Clipboard functionality requires modern browser support
427
+ Revolutionary clipboard image support:
428
+
429
+ ```javascript
430
+ // Usage:
431
+ // 1. Copy image from any source (screenshot, browser, etc.)
432
+ // 2. Focus on the gallery
433
+ // 3. Press Ctrl+V (Cmd+V on Mac)
434
+ // 4. Image is automatically processed and uploaded
435
+
436
+ // Auto-enabled when canEdit={true}
437
+ <GoalImageGallery
438
+ canEdit={true}
439
+ // Clipboard paste is automatically available
440
+ />
441
+ ```
442
+
443
+ **Clipboard Features:**
444
+ - **Cross-Platform**: Works on Windows, Mac, and Linux
445
+ - **Source Agnostic**: Copy from any application
446
+ - **Auto-Processing**: Automatic image processing and optimization
447
+ - **Format Support**: All major image formats
448
+ - **Browser Support**: Modern browsers (Chrome, Firefox, Safari, Edge)
122
449
 
123
450
  ### 3. URL Loading
124
- Users can load images from URLs:
125
- - Enter a direct image URL
126
- - The component validates the URL and loads the image
127
- - Supports all standard image formats
128
451
 
129
- ## Keyboard Shortcuts
452
+ Load images from external URLs:
130
453
 
131
- ### Image Viewer
132
- - `←` / `→`: Navigate between images
133
- - `+` / `=`: Zoom in
134
- - `-`: Zoom out
135
- - `Escape`: Close image viewer
454
+ ```javascript
455
+ // Users can enter image URLs directly
456
+ // Component validates and loads the image
457
+ // Supports CORS-enabled images
458
+
459
+ const handleUrlUpload = async (imageUrl) => {
460
+ try {
461
+ const response = await fetch(imageUrl);
462
+ const blob = await response.blob();
463
+
464
+ // Process the image
465
+ const result = await processImageBlob(blob);
466
+
467
+ return {
468
+ success: true,
469
+ data: result
470
+ };
471
+ } catch (error) {
472
+ return {
473
+ success: false,
474
+ message: 'Failed to load image from URL'
475
+ };
476
+ }
477
+ };
478
+ ```
479
+
480
+ ### 4. Mobile Camera Integration
481
+
482
+ Enhanced mobile support:
483
+
484
+ ```javascript
485
+ // Mobile-specific features:
486
+ // - Camera access
487
+ // - Photo library access
488
+ // - Touch gestures
489
+ // - Responsive design
490
+
491
+ <GoalImageGallery
492
+ canEdit={true}
493
+ // Mobile camera is automatically available on mobile devices
494
+ mobileOptimized={true}
495
+ touchGestures={true}
496
+ />
497
+ ```
498
+
499
+ ## Keyboard Shortcuts & Navigation
500
+
501
+ ### Image Viewer Shortcuts
502
+
503
+ | Shortcut | Action | Context |
504
+ |----------|---------|---------|
505
+ | `←` / `→` | Navigate between images | Image viewer open |
506
+ | `+` / `=` | Zoom in | Image viewer open |
507
+ | `-` | Zoom out | Image viewer open |
508
+ | `Escape` | Close image viewer | Image viewer open |
509
+ | `Space` | Toggle fullscreen | Image viewer open |
510
+ | `Enter` | Open selected image | Gallery focused |
136
511
 
137
512
  ### Gallery Navigation
138
- - `Tab`: Navigate between interactive elements
139
- - `Enter` / `Space`: Activate buttons
140
- - `Escape`: Close dialogs
141
513
 
142
- ## API Integration
514
+ | Shortcut | Action | Context |
515
+ |----------|---------|---------|
516
+ | `Tab` | Navigate between elements | Gallery focused |
517
+ | `Shift + Tab` | Navigate backwards | Gallery focused |
518
+ | `Enter` / `Space` | Activate button/link | Element focused |
519
+ | `Ctrl + V` | Paste from clipboard | Editing enabled |
520
+ | `Delete` | Remove selected image | Image selected |
143
521
 
144
- The component requires an `imageHandlerApi` function that handles image operations:
522
+ ### Accessibility Features
523
+
524
+ ```javascript
525
+ // Built-in accessibility features:
526
+ <GoalImageGallery
527
+ // ARIA labels are automatically generated
528
+ ariaLabel="Product image gallery"
529
+ ariaDescribedBy="gallery-instructions"
530
+
531
+ // Screen reader support
532
+ screenReaderOptimized={true}
533
+
534
+ // High contrast mode
535
+ highContrastMode={true}
536
+
537
+ // Keyboard navigation
538
+ keyboardNavigation={true}
539
+
540
+ // Focus management
541
+ manageFocus={true}
542
+ />
543
+ ```
544
+
545
+ ### Custom Keyboard Shortcuts
546
+
547
+ ```javascript
548
+ import { useKeyboardNavigation } from '@mgonza02/goal-image-gallery';
549
+
550
+ const CustomGallery = () => {
551
+ const { handleKeyPress } = useKeyboardNavigation({
552
+ onPreviousImage: () => console.log('Previous'),
553
+ onNextImage: () => console.log('Next'),
554
+ onCloseImage: () => console.log('Close'),
555
+ onZoomIn: () => console.log('Zoom in'),
556
+ onZoomOut: () => console.log('Zoom out'),
557
+ // Add custom shortcuts
558
+ customShortcuts: {
559
+ 'd': () => downloadCurrentImage(),
560
+ 'i': () => showImageInfo(),
561
+ 'f': () => toggleFullscreen()
562
+ }
563
+ });
564
+
565
+ return (
566
+ <div onKeyDown={handleKeyPress}>
567
+ <GoalImageGallery {...props} />
568
+ </div>
569
+ );
570
+ };
571
+ ```
572
+
573
+ ## API Integration & Data Flow
574
+
575
+ ### API Handler Function
576
+
577
+ The `imageHandlerApi` function is the core of the upload system:
145
578
 
146
579
  ```javascript
147
580
  const handleImageUpload = async (imageData) => {
148
581
  try {
582
+ // imageData contains:
583
+ // - files: Array of File objects
584
+ // - metadata: Additional data (owner, company, etc.)
585
+ // - options: Upload options (compression, format, etc.)
586
+
149
587
  const response = await api.uploadImages({
150
- ...imageData,
151
- companyId: currentCompany,
152
- entityId: ownerEntity.id
588
+ files: imageData.files,
589
+ companyId: imageData.metadata.companyId,
590
+ entityId: imageData.metadata.entityId,
591
+ entityType: imageData.metadata.entityType,
592
+ folder: imageData.options.folder,
593
+ compression: imageData.options.compression,
594
+ thumbnailSize: imageData.options.thumbnailSize
595
+ });
596
+
597
+ // Must return standardized response
598
+ return {
599
+ success: true,
600
+ data: response.data, // Array of uploaded image objects
601
+ message: 'Images uploaded successfully',
602
+ codes: response.data.map(img => img.code) // Optional: image codes
603
+ };
604
+ } catch (error) {
605
+ return {
606
+ success: false,
607
+ message: error.message || 'Upload failed',
608
+ error: error
609
+ };
610
+ }
611
+ };
612
+ ```
613
+
614
+ ### API Response Format
615
+
616
+ Expected response format from your API:
617
+
618
+ ```javascript
619
+ // Successful response
620
+ {
621
+ success: true,
622
+ data: [
623
+ {
624
+ id: 123,
625
+ code: 'img_abc123',
626
+ url: 'https://cdn.example.com/images/img_abc123.jpg',
627
+ thumbnailUrl: 'https://cdn.example.com/images/img_abc123_thumb.jpg',
628
+ filename: 'product-image.jpg',
629
+ size: 1024000,
630
+ width: 1920,
631
+ height: 1080,
632
+ mimeType: 'image/jpeg',
633
+ uploadedAt: '2023-01-01T00:00:00Z'
634
+ }
635
+ ],
636
+ message: 'Upload successful'
637
+ }
638
+
639
+ // Error response
640
+ {
641
+ success: false,
642
+ message: 'File size too large',
643
+ error: {
644
+ code: 'FILE_TOO_LARGE',
645
+ details: 'Maximum file size is 25MB'
646
+ }
647
+ }
648
+ ```
649
+
650
+ ### Real-World API Integration Examples
651
+
652
+ #### 1. REST API Integration
653
+
654
+ ```javascript
655
+ const restApiHandler = async (imageData) => {
656
+ const formData = new FormData();
657
+
658
+ // Add files
659
+ imageData.files.forEach((file, index) => {
660
+ formData.append(`files[${index}]`, file);
661
+ });
662
+
663
+ // Add metadata
664
+ formData.append('companyId', imageData.metadata.companyId);
665
+ formData.append('entityId', imageData.metadata.entityId);
666
+ formData.append('entityType', imageData.metadata.entityType);
667
+
668
+ try {
669
+ const response = await fetch('/api/images/upload', {
670
+ method: 'POST',
671
+ body: formData,
672
+ headers: {
673
+ 'Authorization': `Bearer ${authToken}`,
674
+ // Don't set Content-Type, let browser set it with boundary
675
+ }
676
+ });
677
+
678
+ const result = await response.json();
679
+
680
+ if (!response.ok) {
681
+ throw new Error(result.message || 'Upload failed');
682
+ }
683
+
684
+ return {
685
+ success: true,
686
+ data: result.images,
687
+ message: result.message
688
+ };
689
+ } catch (error) {
690
+ return {
691
+ success: false,
692
+ message: error.message
693
+ };
694
+ }
695
+ };
696
+ ```
697
+
698
+ #### 2. GraphQL Integration
699
+
700
+ ```javascript
701
+ import { useMutation } from '@apollo/client';
702
+
703
+ const UPLOAD_IMAGES = gql`
704
+ mutation UploadImages($files: [Upload!]!, $input: ImageUploadInput!) {
705
+ uploadImages(files: $files, input: $input) {
706
+ success
707
+ message
708
+ images {
709
+ id
710
+ code
711
+ url
712
+ thumbnailUrl
713
+ filename
714
+ size
715
+ width
716
+ height
717
+ }
718
+ }
719
+ }
720
+ `;
721
+
722
+ const GraphQLImageGallery = ({ ownerEntity, ...props }) => {
723
+ const [uploadImages] = useMutation(UPLOAD_IMAGES);
724
+
725
+ const handleImageUpload = async (imageData) => {
726
+ try {
727
+ const { data } = await uploadImages({
728
+ variables: {
729
+ files: imageData.files,
730
+ input: {
731
+ companyId: imageData.metadata.companyId,
732
+ entityId: imageData.metadata.entityId,
733
+ entityType: imageData.metadata.entityType
734
+ }
735
+ }
736
+ });
737
+
738
+ return {
739
+ success: data.uploadImages.success,
740
+ data: data.uploadImages.images,
741
+ message: data.uploadImages.message
742
+ };
743
+ } catch (error) {
744
+ return {
745
+ success: false,
746
+ message: error.message
747
+ };
748
+ }
749
+ };
750
+
751
+ return (
752
+ <GoalImageGallery
753
+ {...props}
754
+ ownerEntity={ownerEntity}
755
+ imageHandlerApi={handleImageUpload}
756
+ />
757
+ );
758
+ };
759
+ ```
760
+
761
+ #### 3. Firebase Storage Integration
762
+
763
+ ```javascript
764
+ import { ref, uploadBytes, getDownloadURL } from 'firebase/storage';
765
+ import { storage } from './firebaseConfig';
766
+
767
+ const firebaseHandler = async (imageData) => {
768
+ try {
769
+ const uploadPromises = imageData.files.map(async (file) => {
770
+ const storageRef = ref(storage, `images/${Date.now()}_${file.name}`);
771
+ const snapshot = await uploadBytes(storageRef, file);
772
+ const downloadURL = await getDownloadURL(snapshot.ref);
773
+
774
+ return {
775
+ id: snapshot.ref.fullPath,
776
+ code: snapshot.ref.name,
777
+ url: downloadURL,
778
+ filename: file.name,
779
+ size: file.size,
780
+ mimeType: file.type
781
+ };
153
782
  });
154
783
 
784
+ const results = await Promise.all(uploadPromises);
785
+
155
786
  return {
156
787
  success: true,
157
- data: response.data,
158
- message: 'Images uploaded successfully'
788
+ data: results,
789
+ message: 'Images uploaded to Firebase'
159
790
  };
160
791
  } catch (error) {
161
792
  return {
@@ -166,6 +797,140 @@ const handleImageUpload = async (imageData) => {
166
797
  };
167
798
  ```
168
799
 
800
+ ### Error Handling Best Practices
801
+
802
+ ```javascript
803
+ const robustApiHandler = async (imageData) => {
804
+ try {
805
+ // Validate files before upload
806
+ const validFiles = imageData.files.filter(file => {
807
+ if (file.size > 25 * 1024 * 1024) {
808
+ console.warn(`File ${file.name} is too large`);
809
+ return false;
810
+ }
811
+ if (!file.type.startsWith('image/')) {
812
+ console.warn(`File ${file.name} is not an image`);
813
+ return false;
814
+ }
815
+ return true;
816
+ });
817
+
818
+ if (validFiles.length === 0) {
819
+ return {
820
+ success: false,
821
+ message: 'No valid files to upload'
822
+ };
823
+ }
824
+
825
+ // Attempt upload with retry logic
826
+ let attempts = 0;
827
+ const maxAttempts = 3;
828
+
829
+ while (attempts < maxAttempts) {
830
+ try {
831
+ const response = await uploadFiles(validFiles);
832
+ return {
833
+ success: true,
834
+ data: response.data,
835
+ message: 'Upload successful'
836
+ };
837
+ } catch (error) {
838
+ attempts++;
839
+ if (attempts >= maxAttempts) {
840
+ throw error;
841
+ }
842
+
843
+ // Wait before retry
844
+ await new Promise(resolve => setTimeout(resolve, 1000 * attempts));
845
+ }
846
+ }
847
+ } catch (error) {
848
+ // Log error for debugging
849
+ console.error('Image upload failed:', error);
850
+
851
+ return {
852
+ success: false,
853
+ message: error.message || 'Upload failed. Please try again.',
854
+ error: error
855
+ };
856
+ }
857
+ };
858
+ ```
859
+ };
860
+ }
861
+ };
862
+ ```
863
+
864
+ ## Customization Options
865
+
866
+ ### Slot System
867
+
868
+ The GoalImageGallery supports a flexible slot system for customization:
869
+
870
+ #### Custom Image Selector
871
+
872
+ You can provide your own image selector component:
873
+
874
+ ```javascript
875
+ // Custom selector component
876
+ const CustomImageSelector = ({
877
+ afterUpload,
878
+ activate,
879
+ onCancel,
880
+ multiple,
881
+ title,
882
+ uploadMessage,
883
+ maxFileSize,
884
+ acceptedFormats,
885
+ customStyles
886
+ }) => {
887
+ const handleFileSelect = async (files) => {
888
+ // Your custom upload logic
889
+ const result = await uploadFiles(files);
890
+ afterUpload(result);
891
+ };
892
+
893
+ return (
894
+ <div style={{ ...customStyles, padding: '20px' }}>
895
+ <h3>{title}</h3>
896
+ <p>{uploadMessage}</p>
897
+ <input
898
+ type="file"
899
+ multiple={multiple}
900
+ accept={acceptedFormats.join(',')}
901
+ onChange={(e) => handleFileSelect(e.target.files)}
902
+ />
903
+ <button onClick={onCancel}>Cancel</button>
904
+ </div>
905
+ );
906
+ };
907
+
908
+ // Usage
909
+ <GoalImageGallery
910
+ // ... other props
911
+ slot={{
912
+ selector: CustomImageSelector
913
+ }}
914
+ slotProps={{
915
+ selector: {
916
+ maxFileSize: 10 * 1024 * 1024,
917
+ acceptedFormats: ['image/jpeg', 'image/png'],
918
+ customStyles: {
919
+ border: '2px dashed #007bff',
920
+ borderRadius: '8px',
921
+ backgroundColor: '#f8f9fa'
922
+ }
923
+ }
924
+ }}
925
+ />
926
+ ```
927
+
928
+ ### Available Slots
929
+
930
+ | Slot | Description | Props Passed |
931
+ |------|-------------|--------------|
932
+ | `selector` | Custom image selector/uploader component | `afterUpload`, `activate`, `onCancel`, `multiple`, `title`, `uploadMessage`, and custom props from `slotProps.selector` |
933
+
169
934
  ## Styling and Theming
170
935
 
171
936
  The component uses Material-UI's theming system and can be customized: