A high-performance, minimizable, and feature-rich file upload widget built with React 19, Tailwind CSS 4, and Zustand.
This project is a modern, production-ready Upload Widget designed to handle file uploads with a polished user experience. It solves the common problem of managing complex file upload states (progress, error, cancellation) in web applications while maintaining a clean and unobtrusive UI.
The widget supports drag-and-drop, real-time progress tracking, and automatic image compression, all wrapped in a collapsible interface that stays out of the user's way.
- 🚀 Drag & Drop Interface: Seamless file selection using
react-dropzone. - 📉 Automatic Image Compression: Client-side image compression to save bandwidth and storage using HTML Canvas.
- 📊 Real-time Progress: Visual feedback for upload progress, total size, and compressed size.
- 🔄 Robust State Management: Powered by Zustand and Immer for handling complex asynchronous flows.
- 🎨 Smooth Animations: Fluid transitions for collapsing/expanding and list updates using Motion.
- ⏯️ Control: Ability to cancel and retry individual uploads.
- 🧩 Minimizable UI: A collapsible "floating" widget that expands only when needed.
sequenceDiagram
participant User
participant Widget as UI Component
participant Store as Zustand Store
participant Utils as Compressor
participant API as Backend API
User->>Widget: Drops File(s)
Widget->>Store: addUploads(files)
Store->>Store: Create Upload ID & Set Status 'progress'
Store->>Utils: compressImage(file)
Utils-->>Store: Compressed File
Store->>API: POST /uploads (multipart/form-data)
loop Upload Progress
API-->>Store: onUploadProgress
Store-->>Widget: Update Progress Bar
end
alt Success
API-->>Store: 200 OK (URL)
Store-->>Widget: Show Success State
else Error
API-->>Store: Error
Store-->>Widget: Show Error / Retry Option
end
- Framework: React 19
- Language: TypeScript
- Build Tool: Vite
- CSS Framework: Tailwind CSS v4
- Components: Radix UI (Collapsible, Progress, Scroll Area)
- Icons: Lucide React
- Animations: Motion
- Utilities:
tailwind-merge,tailwind-variants
- State Management: Zustand + Immer
- HTTP Client: Axios
- File Handling: React Dropzone
Follow these steps to get a local copy running.
- Node.js (v18+ recommended)
- npm, yarn, or pnpm
- Backend Server: The widget expects a backend running at
http://localhost:3333/uploadsto handle thePOSTrequests.
-
Clone the repository
git clone https://github.com/yourusername/upload-widget.git cd upload-widget/web -
Install dependencies
npm install # or pnpm install # or yarn install
-
Start the development server
npm run dev
-
Open in browser Navigate to
http://localhost:5173to see the widget in action.
| Command | Description |
|---|---|
npm run dev |
Starts the local development server with Vite. |
npm run build |
Builds the project for production (TypeScript compile + Vite build). |
npm run lint |
Runs ESLint to check for code quality issues. |
npm run preview |
Previews the production build locally. |
The widget sends a POST request to the configured endpoint. Here is the expected structure:
Request:
- Method:
POST - URL:
http://localhost:3333/uploads - Header:
Content-Type: multipart/form-data - Body:
file: (Binary)
Response (Expected by Widget):
{
"url": "https://storage.example.com/uploads/image-123.png"
}web/
├── src/
│ ├── components/
│ │ ├── ui/ # Generic UI components (Button, Progress Bar)
│ │ ├── upload-widget*.tsx # Core widget components (Header, List, Dropzone)
│ │ └── ...
│ ├── http/ # API integration logic
│ │ └── upload-file-to-store.ts
│ ├── store/ # Global state (Zustand)
│ │ └── uploads.ts
│ ├── utils/ # Helper functions (Compression, Formatting)
│ ├── app.tsx # Main application entry point
│ └── main.tsx # React DOM rendering
├── public/
├── package.json
├── tailwind.config.js
├── tsconfig.json
└── vite.config.ts
Based on the current implementation, here are suggested improvements:
- Environment Variables: Extract the hardcoded API URL (
http://localhost:3333) into a.envfile (e.g.,VITE_API_URL) to support different environments. - Upload Queue Limit: Implement a concurrency limit to prevent freezing the browser when uploading hundreds of files simultaneously.
- File Type Validation: Add stricter validation props to the
UploadWidgetto restrict specific file types (e.g., "Images only" or "PDFs only") before they reach the state.
Made with ❤️ by ChristopherLDO