npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@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

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-gallery
import { 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-gallery

Peer Dependencies

Make sure you have the required peer dependencies installed:

npm install @mui/material @mui/icons-material @emotion/react @emotion/styled react react-dom

Import

// 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

  1. Performance: Use thumbnail versions for gallery display
  2. Accessibility: Always provide meaningful alt text
  3. Error Handling: Implement comprehensive error handling
  4. Permissions: Check user permissions before enabling editing
  5. Validation: Validate file types and sizes on both client and server
  6. Responsive Design: Test on various screen sizes
  7. 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 imageHandlerApi is 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:

  1. Follow the existing code style
  2. Add tests for new features
  3. Update documentation
  4. Test accessibility features
  5. Verify mobile responsiveness
  6. 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.js

Available 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;
}