ultra-image-uploader 0.0.1

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 ADDED
@@ -0,0 +1,201 @@
1
+ # Ultra Image Uploader
2
+
3
+ A modern React component for handling image uploads with drag-and-drop functionality, image preview, and ImgBB integration.
4
+
5
+ ## Features
6
+
7
+ - 🎯 **Easy Integration** - Simple to use with any React/Next.js project
8
+ - 🖼️ **Drag & Drop** - Intuitive drag and drop interface
9
+ - 📸 **Image Preview** - Instant preview of uploaded images
10
+ - ✨ **Multiple Upload** - Support for multiple image uploads
11
+ - 🔍 **File Validation** - Built-in file type and size validation
12
+ - 🎨 **Customizable** - Highly customizable styling and components
13
+ - 🗑️ **Delete Function** - Easy image removal functionality
14
+ - 🔄 **ImgBB Integration** - Built-in support for ImgBB image hosting
15
+
16
+ ## Installation
17
+
18
+ ```bash
19
+ npm install ultra-image-uploaderer
20
+ # or
21
+ yarn add ultra-image-uploaderer
22
+ # or
23
+ pnpm add ultra-image-uploaderer
24
+ ```
25
+
26
+ ## Quick Start
27
+
28
+ ### Basic Usage
29
+
30
+ ```tsx
31
+ import { ImageUploader, imageUrl } from "ultra-image-uploaderer";
32
+ import { useState } from "react";
33
+
34
+ function App() {
35
+ const [images, setImages] = useState<File[]>([]);
36
+
37
+ return (
38
+ <ImageUploader
39
+ images={images}
40
+ setImages={setImages}
41
+ mode="add"
42
+ multiple={true}
43
+ />
44
+ );
45
+ }
46
+ ```
47
+
48
+ ### With ImgBB Integration
49
+
50
+ ```tsx
51
+ import { ImageUploader, imageUrl } from "ultra-image-uploaderer";
52
+ import { useState } from "react";
53
+
54
+ function ImageUploadForm() {
55
+ const [images, setImages] = useState<File[]>([]);
56
+
57
+ const handleSubmit = async () => {
58
+ try {
59
+ const uploadedUrls = await imageUrl(images, "YOUR_IMGBB_API_KEY");
60
+ console.log("Uploaded image URLs:", uploadedUrls);
61
+ } catch (error) {
62
+ console.error("Upload failed:", error);
63
+ }
64
+ };
65
+
66
+ return (
67
+ <form onSubmit={handleSubmit}>
68
+ <ImageUploader
69
+ images={images}
70
+ setImages={setImages}
71
+ mode="add"
72
+ multiple={true}
73
+ />
74
+ <button type="submit">Upload Images</button>
75
+ </form>
76
+ );
77
+ }
78
+ ```
79
+
80
+ ## Component Props
81
+
82
+ | Prop | Type | Default | Description |
83
+ | ----------------------- | ------------------------------------- | ----------------------------- | -------------------------------- |
84
+ | `images` | `File[]` | Required | Array of selected image files |
85
+ | `setImages` | `(images: File[]) => void` | Required | Function to update images array |
86
+ | `mode` | `"add" \| "update"` | Required | Mode of operation |
87
+ | `defaultImages` | `string[]` | `[]` | Array of existing image URLs |
88
+ | `multiple` | `boolean` | `false` | Allow multiple file selection |
89
+ | `maxFileSize` | `number` | `5242880` | Maximum file size in bytes (5MB) |
90
+ | `allowedFileTypes` | `string[]` | `["image/jpeg", "image/png"]` | Allowed MIME types |
91
+ | `containerClassName` | `string` | `""` | Custom container class |
92
+ | `uploadBoxClassName` | `string` | `""` | Custom upload box class |
93
+ | `imageClassName` | `string` | `""` | Custom image preview class |
94
+ | `uploadBoxStyle` | `React.CSSProperties` | `{}` | Custom upload box styles |
95
+ | `imageStyle` | `React.CSSProperties` | `{}` | Custom image preview styles |
96
+ | `uploadIcon` | `React.ReactNode` | `<UploadCloudIcon />` | Custom upload icon |
97
+ | `deleteIcon` | `React.ReactNode` | `<TrashIcon />` | Custom delete icon |
98
+ | `uploadText` | `string` | `"Choose files to upload"` | Upload box text |
99
+ | `dragAndDropText` | `string` | `"Drag and drop files here"` | Drag and drop text |
100
+ | `fileTypeText` | `string` | `"PNG, JPG, or JPEG files"` | File type info text |
101
+ | `onUpload` | `(files: File[]) => void` | - | Upload callback |
102
+ | `onRemove` | `(file: File, index: number) => void` | - | Remove callback |
103
+ | `onFileValidationError` | `(error: string) => void` | - | Validation error callback |
104
+
105
+ ## Usage Examples
106
+
107
+ ### Add Mode (New Upload)
108
+
109
+ ```tsx
110
+ import { ImageUploader, imageUrl } from "ultra-image-uploaderer";
111
+
112
+ function AddImage() {
113
+ const [images, setImages] = useState<File[]>([]);
114
+
115
+ const handleSubmit = async () => {
116
+ const imgUrls = await imageUrl(images, "YOUR_IMGBB_API_KEY");
117
+ // Handle the uploaded image URLs
118
+ };
119
+
120
+ return (
121
+ <form onSubmit={handleSubmit}>
122
+ <ImageUploader
123
+ images={images}
124
+ setImages={setImages}
125
+ mode="add"
126
+ multiple={true}
127
+ uploadBoxClassName="border-3 border-dashed p-5"
128
+ imageClassName="w-20 h-20"
129
+ />
130
+ <button type="submit">Upload</button>
131
+ </form>
132
+ );
133
+ }
134
+ ```
135
+
136
+ ### Update Mode (Edit Existing Images)
137
+
138
+ ```tsx
139
+ function UpdateImage() {
140
+ const [images, setImages] = useState<File[]>([]);
141
+ const existingImages = ["https://example.com/image1.jpg"];
142
+
143
+ const handleSubmit = async () => {
144
+ const newImgUrls = await imageUrl(images, "YOUR_IMGBB_API_KEY");
145
+ // Combine existing and new images
146
+ };
147
+
148
+ return (
149
+ <form onSubmit={handleSubmit}>
150
+ <ImageUploader
151
+ images={images}
152
+ setImages={setImages}
153
+ mode="update"
154
+ multiple={true}
155
+ defaultImages={existingImages}
156
+ uploadBoxClassName="border-3 border-dashed p-5"
157
+ imageClassName="w-20 h-20"
158
+ />
159
+ <button type="submit">Update</button>
160
+ </form>
161
+ );
162
+ }
163
+ ```
164
+
165
+ ## Styling
166
+
167
+ The component uses Tailwind CSS classes by default but can be customized using className props:
168
+
169
+ ```tsx
170
+ <ImageUploader
171
+ images={images}
172
+ setImages={setImages}
173
+ mode="add"
174
+ containerClassName="max-w-2xl mx-auto"
175
+ uploadBoxClassName="border-2 border-dashed border-blue-500 rounded-lg"
176
+ imageClassName="rounded-lg shadow-md"
177
+ />
178
+ ```
179
+
180
+ ## ImgBB Integration
181
+
182
+ The package includes a utility function `imageUrl` for uploading images to ImgBB:
183
+
184
+ ```typescript
185
+ const uploadImages = async (files: File[]) => {
186
+ try {
187
+ const urls = await imageUrl(files, "YOUR_IMGBB_API_KEY");
188
+ console.log("Uploaded URLs:", urls);
189
+ } catch (error) {
190
+ console.error("Upload failed:", error);
191
+ }
192
+ };
193
+ ```
194
+
195
+ ## License
196
+
197
+ MIT
198
+
199
+ ## Contributing
200
+
201
+ Contributions are welcome! Please feel free to submit a Pull Request.
@@ -0,0 +1,12 @@
1
+ export interface ImageUploaderProps {
2
+ images: File[];
3
+ setImages: (images: File[]) => void;
4
+ mode: "add" | "update";
5
+ defaultImages?: string[];
6
+ multiple?: boolean;
7
+ inputStyles?: string;
8
+ containerStyles?: string;
9
+ uploadText?: string;
10
+ typeText?: string;
11
+ }
12
+ export declare function ImageUploader({ images, setImages, mode, defaultImages, multiple, inputStyles, containerStyles, uploadText, typeText, }: ImageUploaderProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,27 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { TrashIcon, UploadCloudIcon } from "lucide-react";
4
+ import { useState } from "react";
5
+ export function ImageUploader({ images, setImages, mode, defaultImages = [], multiple = false, inputStyles = "", containerStyles = "", uploadText = "Browse files or drag & drop", typeText = "PNG, JPG, JPEG, WEBP", }) {
6
+ const [removedDefaultImages, setRemovedDefaultImages] = useState([]);
7
+ console.log(removedDefaultImages);
8
+ const handleImageChange = (e) => {
9
+ if (e.target.files && e.target.files.length > 0) {
10
+ const files = Array.from(e.target.files);
11
+ if (!multiple && files.length > 1) {
12
+ alert("Only one image can be uploaded at a time.");
13
+ return;
14
+ }
15
+ setImages(multiple ? [...images, ...files] : [files[0]]);
16
+ }
17
+ };
18
+ const removeImage = (index) => {
19
+ setImages(images.filter((_, i) => i !== index));
20
+ };
21
+ // Function to remove a default image
22
+ const removeDefaultImage = (index) => {
23
+ setRemovedDefaultImages((prev) => [...prev, index]);
24
+ };
25
+ return (_jsx("div", { className: `space-y-4 ${containerStyles}`, children: _jsxs("div", { className: "flex flex-wrap gap-4", children: [_jsxs("div", { className: `${inputStyles} h-40 w-full bg-gray-100 border border-gray-200 cursor-pointer flex justify-center items-center text-white text-center relative rounded-xs duration-300`, children: [_jsxs("div", { className: "flex flex-col items-center text-black", children: [_jsx(UploadCloudIcon, { className: "text-3xl text-gray-900 mb-1" }), _jsx("span", { className: "text-sm text-gray-800 font-semibold", children: uploadText }), _jsx("span", { className: "text-gray-600 text-xs", children: typeText })] }), _jsx("input", { type: "file", accept: "image/*", multiple: multiple, onChange: handleImageChange, className: "absolute inset-0 opacity-0 cursor-pointer rounded-lg" })] }), mode === "update" &&
26
+ defaultImages.map((url, index) => !removedDefaultImages.includes(index) && (_jsxs("div", { className: "relative w-fit", children: [_jsx("img", { src: url, alt: `Existing Image ${index + 1}`, width: 100, height: 100, className: "h-32 w-32 object-cover rounded-sm border border-gray-200 cursor-pointer hover:shadow-lg transition-shadow duration-200" }), _jsx(TrashIcon, { onClick: () => removeDefaultImage(index), className: "absolute -top-2 -right-2 rounded-full bg-red-600 text-2xl text-white cursor-pointer p-1 hover:bg-red-700 transition-colors duration-200" })] }, `default-${index}`))), images.map((image, index) => (_jsxs("div", { className: "relative w-fit", children: [_jsx("img", { src: URL.createObjectURL(image), alt: `Preview ${index + 1}`, width: 100, height: 100, className: "h-32 w-32 object-cover rounded-sm border border-gray-200 cursor-pointer hover:shadow-lg duration-200" }), _jsx(TrashIcon, { onClick: () => removeImage(index), className: "absolute -top-2 -right-2 rounded-full bg-red-600 text-2xl text-white cursor-pointer p-1 hover:bg-red-700 transition-colors duration-200" })] }, index)))] }) }));
27
+ }
@@ -0,0 +1,4 @@
1
+ export { ImageUploader } from './components/ImageUploader';
2
+ export type { ImageUploaderProps } from './components/ImageUploader';
3
+ export { uploadImagesToImageBB } from './utils/imageUpload';
4
+ export type { ImageBBUrlResult } from './utils/imageUpload';
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export { ImageUploader } from './components/ImageUploader';
2
+ export { uploadImagesToImageBB } from './utils/imageUpload';
@@ -0,0 +1,10 @@
1
+ export interface ImageBBResponse {
2
+ success: boolean;
3
+ data: {
4
+ url: string;
5
+ };
6
+ }
7
+ export interface ImageBBUrlResult {
8
+ urls: string[];
9
+ }
10
+ export declare const uploadImagesToImageBB: (images: File[], apiKey: string) => Promise<ImageBBUrlResult>;
@@ -0,0 +1,31 @@
1
+ const uploadImageToImageBB = async (imageFile, apiKey) => {
2
+ const formData = new FormData();
3
+ formData.append("image", imageFile);
4
+ try {
5
+ const response = await fetch(`https://api.imgbb.com/1/upload?key=${apiKey}`, {
6
+ method: "POST",
7
+ body: formData,
8
+ });
9
+ const data = await response.json();
10
+ if (data.success) {
11
+ return data.data.url;
12
+ }
13
+ else {
14
+ throw new Error("Image upload failed");
15
+ }
16
+ }
17
+ catch (error) {
18
+ console.error("Error uploading image:", error);
19
+ alert("❌ Failed to upload image. Please try again.");
20
+ return null;
21
+ }
22
+ };
23
+ export const uploadImagesToImageBB = async (images, apiKey) => {
24
+ const imageURLs = (await Promise.all(images.map((image) => uploadImageToImageBB(image, apiKey)))).filter((url) => url !== null);
25
+ if (imageURLs.length === 0) {
26
+ throw new Error("Failed to upload images");
27
+ }
28
+ return {
29
+ urls: imageURLs,
30
+ };
31
+ };
package/package.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "ultra-image-uploader",
3
+ "version": "0.0.1",
4
+ "description": "React component for image uploads with drag-and-drop and ImgBB integration",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "scripts": {
8
+ "build": "tsc",
9
+ "prepare": "npm run build"
10
+ },
11
+ "dependencies": {
12
+ "@types/node": "^22.13.14",
13
+ "lucide-react": "^0.485.0",
14
+ "react": "^19.1.0",
15
+ "react-dom": "^19.1.0"
16
+ },
17
+ "devDependencies": {
18
+ "@types/react": "^19.1.1"
19
+ },
20
+ "keywords": [
21
+ "react",
22
+ "form",
23
+ "typescript"
24
+ ],
25
+ "author": "Digontha Das",
26
+ "license": "MIT"
27
+ }
@@ -0,0 +1,115 @@
1
+ "use client";
2
+ import { TrashIcon, UploadCloudIcon } from "lucide-react";
3
+ import { useState } from "react";
4
+
5
+ export interface ImageUploaderProps {
6
+ images: File[];
7
+ setImages: (images: File[]) => void;
8
+ mode: "add" | "update";
9
+ defaultImages?: string[];
10
+ multiple?: boolean;
11
+ inputStyles?: string;
12
+ containerStyles?: string;
13
+ uploadText?: string;
14
+ typeText?: string;
15
+ }
16
+
17
+ export function ImageUploader({
18
+ images,
19
+ setImages,
20
+ mode,
21
+ defaultImages = [],
22
+ multiple = false,
23
+ inputStyles = "",
24
+ containerStyles = "",
25
+ uploadText = "Browse files or drag & drop",
26
+ typeText = "PNG, JPG, JPEG, WEBP",
27
+ }: ImageUploaderProps) {
28
+ const [removedDefaultImages, setRemovedDefaultImages] = useState<number[]>(
29
+ []
30
+ );
31
+ console.log(removedDefaultImages);
32
+ const handleImageChange = (e: React.ChangeEvent<HTMLInputElement>) => {
33
+ if (e.target.files && e.target.files.length > 0) {
34
+ const files = Array.from(e.target.files);
35
+ if (!multiple && files.length > 1) {
36
+ alert("Only one image can be uploaded at a time.");
37
+ return;
38
+ }
39
+ setImages(multiple ? [...images, ...files] : [files[0]]);
40
+ }
41
+ };
42
+
43
+ const removeImage = (index: number) => {
44
+ setImages(images.filter((_, i) => i !== index));
45
+ };
46
+
47
+ // Function to remove a default image
48
+ const removeDefaultImage = (index: number) => {
49
+ setRemovedDefaultImages((prev) => [...prev, index]);
50
+ };
51
+
52
+ return (
53
+ <div className={`space-y-4 ${containerStyles}`}>
54
+ <div className="flex flex-wrap gap-4">
55
+ {/* Upload Box */}
56
+ <div
57
+ className={`${inputStyles} h-40 w-full bg-gray-100 border border-gray-200 cursor-pointer flex justify-center items-center text-white text-center relative rounded-xs duration-300`}
58
+ >
59
+ <div className="flex flex-col items-center text-black">
60
+ <UploadCloudIcon className="text-3xl text-gray-900 mb-1" />
61
+ <span className="text-sm text-gray-800 font-semibold">
62
+ {uploadText}
63
+ </span>
64
+ <span className="text-gray-600 text-xs">{typeText}</span>
65
+ </div>
66
+ <input
67
+ type="file"
68
+ accept="image/*"
69
+ multiple={multiple}
70
+ onChange={handleImageChange}
71
+ className="absolute inset-0 opacity-0 cursor-pointer rounded-lg"
72
+ />
73
+ </div>
74
+
75
+ {/* Default Images (Update Mode) */}
76
+ {mode === "update" &&
77
+ defaultImages.map(
78
+ (url, index) =>
79
+ !removedDefaultImages.includes(index) && (
80
+ <div key={`default-${index}`} className="relative w-fit">
81
+ <img
82
+ src={url}
83
+ alt={`Existing Image ${index + 1}`}
84
+ width={100}
85
+ height={100}
86
+ className="h-32 w-32 object-cover rounded-sm border border-gray-200 cursor-pointer hover:shadow-lg transition-shadow duration-200"
87
+ />
88
+ <TrashIcon
89
+ onClick={() => removeDefaultImage(index)}
90
+ className="absolute -top-2 -right-2 rounded-full bg-red-600 text-2xl text-white cursor-pointer p-1 hover:bg-red-700 transition-colors duration-200"
91
+ />
92
+ </div>
93
+ )
94
+ )}
95
+
96
+ {/* Uploaded Images */}
97
+ {images.map((image, index) => (
98
+ <div key={index} className="relative w-fit">
99
+ <img
100
+ src={URL.createObjectURL(image)}
101
+ alt={`Preview ${index + 1}`}
102
+ width={100}
103
+ height={100}
104
+ className="h-32 w-32 object-cover rounded-sm border border-gray-200 cursor-pointer hover:shadow-lg duration-200"
105
+ />
106
+ <TrashIcon
107
+ onClick={() => removeImage(index)}
108
+ className="absolute -top-2 -right-2 rounded-full bg-red-600 text-2xl text-white cursor-pointer p-1 hover:bg-red-700 transition-colors duration-200"
109
+ />
110
+ </div>
111
+ ))}
112
+ </div>
113
+ </div>
114
+ );
115
+ }
package/src/index.ts ADDED
@@ -0,0 +1,4 @@
1
+ export { ImageUploader } from './components/ImageUploader';
2
+ export type { ImageUploaderProps } from './components/ImageUploader';
3
+ export { uploadImagesToImageBB } from './utils/imageUpload';
4
+ export type { ImageBBUrlResult } from './utils/imageUpload';
@@ -0,0 +1,56 @@
1
+ export interface ImageBBResponse {
2
+ success: boolean;
3
+ data: {
4
+ url: string;
5
+ };
6
+
7
+ }
8
+ export interface ImageBBUrlResult {
9
+ urls: string[];
10
+ }
11
+ const uploadImageToImageBB = async (
12
+ imageFile: File,
13
+ apiKey: string
14
+ ): Promise<string | null> => {
15
+ const formData = new FormData();
16
+ formData.append("image", imageFile);
17
+
18
+ try {
19
+ const response = await fetch(
20
+ `https://api.imgbb.com/1/upload?key=${apiKey}`,
21
+ {
22
+ method: "POST",
23
+ body: formData,
24
+ }
25
+ );
26
+
27
+ const data: ImageBBResponse = await response.json();
28
+
29
+ if (data.success) {
30
+ return data.data.url;
31
+ } else {
32
+ throw new Error("Image upload failed");
33
+ }
34
+ } catch (error) {
35
+ console.error("Error uploading image:", error);
36
+ alert("❌ Failed to upload image. Please try again.");
37
+ return null;
38
+ }
39
+ };
40
+
41
+ export const uploadImagesToImageBB = async (
42
+ images: File[],
43
+ apiKey: string
44
+ ): Promise<ImageBBUrlResult> => {
45
+ const imageURLs = (
46
+ await Promise.all(images.map((image) => uploadImageToImageBB(image, apiKey)))
47
+ ).filter((url): url is string => url !== null);
48
+
49
+ if (imageURLs.length === 0) {
50
+ throw new Error("Failed to upload images");
51
+ }
52
+
53
+ return {
54
+ urls: imageURLs,
55
+ };
56
+ };
package/tsconfig.json ADDED
@@ -0,0 +1,17 @@
1
+ {
2
+ "compilerOptions": {
3
+ "outDir": "./dist",
4
+ "rootDir": "./src",
5
+ "esModuleInterop": true,
6
+ "module": "ESNext",
7
+ "moduleResolution": "Node",
8
+ "target": "ESNext",
9
+ "lib": ["DOM", "ESNext", "DOM.Iterable"],
10
+ "declaration": true,
11
+ "declarationDir": "./dist",
12
+ "jsx": "react-jsx",
13
+ "strict": true,
14
+ "resolveJsonModule": true,
15
+ }
16
+
17
+ }