JSPM

@easy-lms/react-image-crop-uploader

1.1.0
  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 87
  • Score
    100M100P100Q66003F
  • License MIT

A comprehensive React image upload component with modal-based cropping capabilities and selective upload support

Package Exports

  • @easy-lms/react-image-crop-uploader
  • @easy-lms/react-image-crop-uploader/dist/react-image-crop-uploader.css
  • @easy-lms/react-image-crop-uploader/style.css

Readme

React Image Crop Uploader

npm version npm downloads License: MIT Live Demo

This repository is a direct fork of the original react-image-crop-uploader.

A comprehensive React image upload component with modal-based cropping capabilities. This library provides an all-in-one solution for handling image uploads with cropping functionality in your React applications.

๐Ÿ“š Live Demo & Examples

๐Ÿš€ View Live Demo - Complete sample project with multiple examples

The sample repository includes:

  • Basic upload functionality
  • Cropping with different aspect ratios
  • Initial images handling
  • Direct upload integration
  • Edit mode examples
  • TypeScript usage examples

Features

  • ๐Ÿ“ Multi-file Support: Upload single or multiple images
  • ๐Ÿ“ Image Cropping: Modal-based cropping with real-time preview using react-easy-crop
  • ๐ŸŽฏ Configurable Crop: Custom aspect ratios and dimensions
  • ๐Ÿ“ค Upload Modes: Direct upload or form integration with selective upload support
  • ๐Ÿ–ผ๏ธ Image Preview: Thumbnail previews with edit functionality
  • โœ‚๏ธ Re-cropping: Edit existing images after upload
  • ๐Ÿ”„ Selective Upload: Smart upload that distinguishes between initial images and new files
  • ๐ŸŽจ Customizable UI: Fully styled with standard CSS
  • ๐Ÿ“ฑ Responsive: Mobile-friendly design
  • ๐Ÿ”ง TypeScript: Full TypeScript support with type definitions
  • ๐Ÿงช Tested: Comprehensive test suite

Installation

npm install @alekpr/react-image-crop-uploader

or

yarn add @alekpr/react-image-crop-uploader

Clone Sample Project

To quickly get started with a working example:

git clone https://github.com/alekpr/react-image-upload-library-sample.git
cd react-image-upload-library-sample
npm install
npm start

Quick Start

๐Ÿ’ก Tip: Check out the complete sample project for more examples and usage patterns!

import { ImageUploader } from '@alekpr/react-image-crop-uploader';
import '@alekpr/react-image-crop-uploader/style.css';

function App() {
  const handleUploadComplete = ({ unuploadedFiles, uploadResponse }) => {
    console.log('New files uploaded:', unuploadedFiles);
    console.log('Server response:', uploadResponse);
  };

  return (
    <ImageUploader
      uploadUrl="/api/upload"
      onUploadComplete={handleUploadComplete}
      enableCrop={true}
      maxFiles={5}
      initialImages={["existing-image.jpg"]} // Won't be re-uploaded
    />
  );
}

Usage

Getting Started

For complete examples and advanced usage patterns, visit our Sample Project Repository.

Basic Usage

import { ImageUploader } from '@alekpr/react-image-crop-uploader';
import '@alekpr/react-image-crop-uploader/style.css';

function App() {
  const handleFilesChange = (files: File[]) => {
    console.log('Selected files:', files);
  };

  return (
    <ImageUploader
      maxFileSize={5}
      maxFiles={3}
      onFilesChange={handleFilesChange}
    />
  );
}

With Cropping Enabled

import { ImageUploader } from '@alekpr/react-image-crop-uploader';
import '@alekpr/react-image-crop-uploader/style.css';

function App() {
  const handleFilesChange = (files: File[]) => {
    console.log('Selected files:', files);
  };

  const handleCropComplete = (croppedFile: File, originalFile: File) => {
    console.log('Cropped file:', croppedFile);
  };

  return (
    <ImageUploader
      maxFileSize={10}
      maxFiles={5}
      enableCrop={true}
      cropAspectRatio={16/9}
      onFilesChange={handleFilesChange}
      onCropComplete={handleCropComplete}
    />
  );
}

Upload Button Features

The component automatically shows an upload button when files are present. You can customize the upload button behavior:

import { ImageUploader } from '@alekpr/react-image-crop-uploader';
import '@alekpr/react-image-crop-uploader/style.css';

function App() {
  return (
    <ImageUploader
      uploadUrl="/api/images/upload"
      enableCrop={true}
      // Upload button configuration
      showUploadButton={true}           // Show/hide upload button (default: true)
      uploadButtonText="Send Files"     // Custom button text (default: "Upload")
      uploadButtonClassName="px-6 py-3 bg-green-500 text-white rounded-lg hover:bg-green-600"
      onUploadComplete={(response) => console.log('Upload completed:', response)}
    />
  );
}

Upload Button Features:

  • โœ… Auto-display: Shows automatically when files are present and uploadUrl is provided
  • โœ… File count: Displays file count for multiple files (e.g., "Upload (3 files)")
  • โœ… Customizable: Support custom text and CSS styling
  • โœ… Smart disable: Automatically disabled when no files are present
  • โœ… Hide option: Can be hidden with showUploadButton={false} for manual upload handling

Direct Upload Mode with Selective Upload

import { ImageUploader } from '@alekpr/react-image-crop-uploader';
import '@alekpr/react-image-crop-uploader/style.css';

function App() {
  const handleUploadComplete = ({ unuploadedFiles, uploadResponse }) => {
    console.log('Files that were uploaded:', unuploadedFiles);
    console.log('Server response:', uploadResponse);
  };

  const handleError = (error: string) => {
    console.error('Upload error:', error);
  };

  const handleUploadProgress = (progress: number) => {
    console.log(`Upload progress: ${progress}%`);
  };

  return (
    <ImageUploader
      uploadUrl="/api/images/upload"
      maxFileSize={10}
      enableCrop={true}
      uploadFieldName="image"
      multipleFileStrategy="single-request"
      uploadButtonText="Upload New Images"
      showUploadButton={true}
      initialImages={["existing-image1.jpg", "existing-image2.jpg"]}
      onUploadComplete={handleUploadComplete}
      onError={handleError}
      onUploadProgress={handleUploadProgress}
    />
  );
}

Selective Upload Features:

  • โœ… Smart Upload: Only uploads new files, skips initial images
  • โœ… Enhanced Callback: Provides both uploaded files and server response
  • โœ… Initial Image Support: Display existing images without re-uploading
  • โœ… Mixed Content: Handle both initial images and new uploads in one component

### Multiple File Upload Strategies

```tsx
import { ImageUploader } from '@alekpr/react-image-crop-uploader';
import '@alekpr/react-image-crop-uploader/style.css';

// Strategy 1: Send all files in a single request
function SingleRequestUpload() {
  return (
    <ImageUploader
      uploadUrl="/api/images/upload-multiple"
      maxFiles={5}
      multipleFileStrategy="single-request"
      uploadFieldName="images"
      onUploadComplete={(response) => console.log('All files uploaded:', response)}
    />
  );
}

// Strategy 2: Send each file in separate requests
function MultipleRequestsUpload() {
  return (
    <ImageUploader
      uploadUrl="/api/images/upload"
      maxFiles={5}
      multipleFileStrategy="multiple-requests"
      uploadFieldName="image"
      onUploadComplete={(responses) => console.log('Upload responses:', responses)}
    />
  );
}

### Enhanced Edit Mode with Selective Upload

```tsx
import { ImageUploader } from '@alekpr/react-image-crop-uploader';
import '@alekpr/react-image-crop-uploader/style.css';

function App() {
  const [uploadedImages, setUploadedImages] = useState<string[]>([
    "https://example.com/image1.jpg",
    "https://example.com/image2.jpg"
  ]);

  const handleUploadComplete = ({ unuploadedFiles, uploadResponse }) => {
    console.log('New files uploaded:', unuploadedFiles);
    console.log('Server response:', uploadResponse);

    // Add new uploaded image URLs to the collection
    if (uploadResponse?.imageUrls) {
      setUploadedImages(prev => [...prev, ...uploadResponse.imageUrls]);
    }
  };

  const handleFilesChange = (files: File[]) => {
    console.log('Current files in component:', files);
  };

  return (
    <ImageUploader
      editMode={true}
      initialImages={uploadedImages} // Existing images won't be re-uploaded
      uploadUrl="/api/images/upload"
      enableCrop={true}
      maxFiles={10}
      showEditButton={true}
      uploadButtonText="Upload New Images"
      onFilesChange={handleFilesChange}
      onUploadComplete={handleUploadComplete}
    />
  );
}

Enhanced Edit Mode Features:

  • โœ… Selective Upload: Only uploads new files, preserves initial images
  • โœ… Mixed Content: Display existing images alongside new uploads
  • โœ… Enhanced Callback: Get both uploaded files and server response details
  • โœ… Smart State Management: Automatically handles initial vs new file distinction
  • โœ… Full Editing: Crop, remove, and add images in the same interface

Props

Upload Configuration

Prop Type Default Description
uploadUrl string undefined URL to upload files to
maxFileSize number 5 Maximum file size in MB
maxFiles number 1 Maximum number of files
acceptedTypes string[] ['image/jpeg', 'image/png', 'image/webp'] Accepted file types
uploadFieldName string 'image' FormData field name for uploaded files
multipleFileStrategy 'single-request' | 'multiple-requests' 'single-request' How to handle multiple file uploads

Crop Configuration

Prop Type Default Description
cropAspectRatio `number 'free'` 'free'
cropSize { width: number; height: number } undefined Fixed crop size
enableCrop boolean false Enable cropping functionality
cropModalTitle string 'Crop Image' Title for crop modal

Initial State

Prop Type Default Description
initialImages `string[] File[]` []
editMode boolean false Enable edit mode

Callbacks

Prop Type Description
onUploadComplete (result: { unuploadedFiles: File[]; uploadResponse: any }) => void Called when upload completes with enhanced data
onFilesChange (files: File[]) => void Called when files change
onError (error: string) => void Called when errors occur
onCropComplete (croppedFile: File, originalFile: File, index?: number) => void Called when cropping completes
onCropModalOpen (file: File, index?: number) => void Called when crop modal opens
onCropModalClose () => void Called when crop modal closes
onUploadProgress (progress: number) => void Called with upload progress (0-100)

UI Configuration

Prop Type Default Description
placeholder string 'Drag & drop images here or click to select' Placeholder text
singleFileHint string Select an image Text shown to guide the user when only a single file can be selected
multipleFilesHint string Select multiple images Text shown to guide the user when multiple files can be selected
disabled boolean false Disable the component
className string '' Custom CSS class
showEditButton boolean true Show edit button on previews
editButtonText string 'Edit' Text for edit button
deleteButtonText string Delete Text for delete button
showUploadButton boolean true Show upload button when files are present and uploadUrl is provided
uploadButtonText string 'Upload' Text for upload button. Shows file count for multiple files
uploadButtonClassName string '' Custom CSS class for upload button. Falls back to default styling if empty
uploadButtonText string 'Upload' Text for upload button
uploadButtonClassName string '' Custom CSS class for upload button
croppedBadgeText string Cropped Text label displayed on images that have been cropped
Prop Type Default Description
cropModalProps object {} Configuration for crop modal
cropModalProps.title string 'Crop Image' Modal title
cropModalProps.saveButtonText string 'Save' Save button text
cropModalProps.cancelButtonText string 'Cancel' Cancel button text
cropModalProps.resetButtonText string 'Reset' Reset button text
cropModalProps.zoomControlText string 'Zoom:' Zoom control text

## Selective Upload Features

The library provides intelligent upload handling that distinguishes between initial images and newly added files:

### How Selective Upload Works

```tsx
import { ImageUploader } from '@alekpr/react-image-crop-uploader';

function App() {
  const existingImages = [
    "https://example.com/existing1.jpg",
    "https://example.com/existing2.jpg"
  ];

  const handleUploadComplete = ({ unuploadedFiles, uploadResponse }) => {
    // unuploadedFiles: Only the new files that were actually uploaded
    // uploadResponse: Server response from the upload request
    console.log('New files uploaded:', unuploadedFiles.length);
    console.log('Server response:', uploadResponse);
  };

  return (
    <ImageUploader
      initialImages={existingImages}  // These won't be uploaded again
      uploadUrl="/api/upload"
      onUploadComplete={handleUploadComplete}
    />
  );
}

Initial Images vs New Files

Initial Images New Files
โœ… Displayed in gallery โœ… Displayed in gallery
โœ… Can be cropped/edited โœ… Can be cropped/edited
โœ… Can be removed โœ… Can be removed
โŒ Not uploaded again โœ… Uploaded to server
๐Ÿท๏ธ Marked as isUploaded: true ๐Ÿท๏ธ Marked as isUploaded: false

Enhanced Upload Callback

The onUploadComplete callback now provides more detailed information:

onUploadComplete={({ unuploadedFiles, uploadResponse }) => {
  // unuploadedFiles: Array of File objects that were just uploaded
  // uploadResponse: The actual server response

  console.log(`${unuploadedFiles.length} new files uploaded`);
  console.log('Server response:', uploadResponse);

  // Handle the response based on your API structure
  if (uploadResponse.success) {
    // Update your state with new image URLs
    setImages(prev => [...prev, ...uploadResponse.imageUrls]);
  }
}}

Use Cases

1. Profile Picture with History

// Show current profile picture + allow new uploads
<ImageUploader
  initialImages={[user.profilePicture]}
  maxFiles={1}
  uploadUrl="/api/profile/upload"
  onUploadComplete={({ uploadResponse }) => {
    // Only new profile picture is uploaded
    updateUserProfile(uploadResponse.imageUrl);
  }}
/>

2. Product Gallery Management

// Edit product images + add new ones
<ImageUploader
  initialImages={product.imageUrls}
  maxFiles={10}
  uploadUrl="/api/products/images"
  onUploadComplete={({ unuploadedFiles, uploadResponse }) => {
    // Only new images are uploaded to server
    const newImageUrls = uploadResponse.imageUrls;
    updateProduct({
      imageUrls: [...product.imageUrls, ...newImageUrls]
    });
  }}
/>

3. Document Attachment System

// Show existing attachments + upload new ones
<ImageUploader
  initialImages={document.attachments}
  uploadUrl="/api/documents/attach"
  onUploadComplete={({ unuploadedFiles, uploadResponse }) => {
    // Track only newly uploaded files
    console.log(`Added ${unuploadedFiles.length} new attachments`);
    refreshDocumentData();
  }}
/>

Upload Button Behavior

The upload button is intelligently managed by the component:

When Upload Button Shows

  • โœ… Files are present (files.length > 0)
  • โœ… Upload URL is provided (uploadUrl prop)
  • โœ… Upload button is not hidden (showUploadButton={true})

Upload Button Features

  • Smart File Count: Shows total files in gallery (e.g., "Upload (5 files)")
    • Includes both initial images and new files in the count
    • Helps users understand how many files are in the current session
  • Selective Upload: Only uploads new files, regardless of total count shown
  • Auto Disable: Automatically disabled when no files are present
  • Custom Styling: Use uploadButtonClassName for custom CSS classes
  • Fallback Styling: Uses default blue styling when uploadButtonClassName is empty
  • Manual Control: Set showUploadButton={false} to handle uploads programmatically

Upload Button Examples

// Basic upload with file count (shows total files including initial)
<ImageUploader
  uploadUrl="/api/upload"
  initialImages={["existing1.jpg", "existing2.jpg"]}
  // Button shows "Upload (4 files)" when 2 new files are added
  // But only uploads the 2 new files
/>

// Custom text with selective upload
<ImageUploader
  uploadUrl="/api/upload"
  uploadButtonText="Upload New Images"
  initialImages={existingImageUrls}
  onUploadComplete={({ unuploadedFiles, uploadResponse }) => {
    console.log(`Uploaded ${unuploadedFiles.length} new images`);
  }}
/>

// Custom styling with smart upload
<ImageUploader
  uploadUrl="/api/upload"
  uploadButtonClassName="custom-upload-btn"
  initialImages={currentImages}
  // Only new files are uploaded, existing ones are preserved
  // Default styling uses modern CSS classes with animations
/>

// Hidden upload button (manual upload with selective logic)
<ImageUploader
  uploadUrl="/api/upload"
  showUploadButton={false}
  initialImages={existingImages}
  onFilesChange={(files) => {
    // files contains both initial images and new files
    // But you can trigger upload manually and only new files will be sent
  }}
/>

Styling

The component comes with modern, built-in styling including:

Built-in CSS Classes

  • โœ… Modern Upload Button: .upload-button with hover effects and animations
  • โœ… Responsive Design: Mobile-friendly button sizing
  • โœ… Accessibility: Focus states and proper contrast ratios
  • โœ… Loading States: Disabled state styling
  • โœ… File Count Display: Stylized file count badges

CSS Import

Import the CSS file to use the built-in styling:

import '@alekpr/react-image-crop-uploader/style.css';

Or import the CSS in your own stylesheet:

@import '@alekpr/react-image-crop-uploader/style.css';

Custom Styling

You can override the default upload button styling:

// Use your own CSS class
<ImageUploader
  uploadUrl="/api/upload"
  uploadButtonClassName="my-custom-button"
/>
/* Your custom button styles */
.my-custom-button {
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  border: none;
  border-radius: 25px;
  padding: 12px 24px;
  color: white;
  font-weight: 600;
  transition: all 0.3s ease;
}

.my-custom-button:hover:not(:disabled) {
  transform: translateY(-2px);
  box-shadow: 0 8px 25px rgba(102, 126, 234, 0.4);
}

Default Button Classes

The built-in upload button uses these CSS classes:

  • .upload-button-container - Container wrapper
  • .upload-button - Main button styling
  • .upload-button-text - Button text styling
  • .upload-file-count - File count badge styling

You can also import the CSS file directly from the dist folder:

import '@alekpr/react-image-crop-uploader/dist/react-image-crop-uploader.css';

TypeScript Support

This library is written in TypeScript and provides full type definitions. All props and callbacks are properly typed.

Browser Support

  • Chrome 60+
  • Firefox 55+
  • Safari 12+
  • Edge 79+

Dependencies

This library uses react-easy-crop for the cropping functionality, which provides a robust and feature-rich cropping experience.

๐Ÿ”— Resources

Contributing

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/AmazingFeature)
  3. Commit your changes (git commit -m 'Add some AmazingFeature')
  4. Push to the branch (git push origin feature/AmazingFeature)
  5. Open a pull request

License

MIT ยฉ alekpr

Support

Getting Help

Common Issues

Before opening an issue, please check the sample project to see if your use case is covered.

If you encounter any issues or have questions, please file an issue on GitHub with:

  1. Steps to reproduce
  2. Expected behavior
  3. Code example (preferably a minimal reproduction)