img-upload-kit 1.1.1 → 2.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.
Files changed (111) hide show
  1. package/CHANGELOG.md +89 -74
  2. package/MIGRATION.md +192 -0
  3. package/README.md +243 -259
  4. package/dist/bin.js +1186 -0
  5. package/dist/bin.js.map +1 -0
  6. package/dist/blur.cjs +40 -412
  7. package/dist/blur.cjs.map +1 -1
  8. package/dist/blur.d.cts +5 -5
  9. package/dist/blur.d.ts +5 -5
  10. package/dist/blur.js +39 -393
  11. package/dist/blur.js.map +1 -1
  12. package/dist/blurDataUtils-DveBRtqE.d.cts +43 -0
  13. package/dist/blurDataUtils-yx9Q6HlU.d.ts +43 -0
  14. package/dist/blurService-D5TpMNji.d.ts +83 -0
  15. package/dist/blurService-DacwylJx.d.cts +83 -0
  16. package/dist/chunk-25GYKQ6J.js +158 -0
  17. package/dist/chunk-25GYKQ6J.js.map +1 -0
  18. package/dist/chunk-34L4TEE3.cjs +226 -0
  19. package/dist/chunk-34L4TEE3.cjs.map +1 -0
  20. package/dist/chunk-DFCSN3X7.cjs +158 -0
  21. package/dist/chunk-DFCSN3X7.cjs.map +1 -0
  22. package/dist/chunk-EWVIL57J.js +294 -0
  23. package/dist/chunk-EWVIL57J.js.map +1 -0
  24. package/dist/chunk-FP5YIWG5.cjs +135 -0
  25. package/dist/chunk-FP5YIWG5.cjs.map +1 -0
  26. package/dist/chunk-HILELQGS.js +226 -0
  27. package/dist/chunk-HILELQGS.js.map +1 -0
  28. package/dist/chunk-IDLLCVGB.js +2370 -0
  29. package/dist/chunk-IDLLCVGB.js.map +1 -0
  30. package/dist/chunk-J6A44Y3W.js +514 -0
  31. package/dist/chunk-J6A44Y3W.js.map +1 -0
  32. package/dist/chunk-LOSVFO56.cjs +145 -0
  33. package/dist/chunk-LOSVFO56.cjs.map +1 -0
  34. package/dist/chunk-MBQJ6NVJ.cjs +294 -0
  35. package/dist/chunk-MBQJ6NVJ.cjs.map +1 -0
  36. package/dist/chunk-MHGQEBPW.js +145 -0
  37. package/dist/chunk-MHGQEBPW.js.map +1 -0
  38. package/dist/chunk-QHU7FUO4.cjs +37 -0
  39. package/dist/chunk-QHU7FUO4.cjs.map +1 -0
  40. package/dist/chunk-S2IFAWJK.js +37 -0
  41. package/dist/chunk-S2IFAWJK.js.map +1 -0
  42. package/dist/chunk-TA5Y64JM.cjs +2370 -0
  43. package/dist/chunk-TA5Y64JM.cjs.map +1 -0
  44. package/dist/chunk-VYVZVUNI.js +135 -0
  45. package/dist/chunk-VYVZVUNI.js.map +1 -0
  46. package/dist/chunk-XKQMCY5Z.cjs +514 -0
  47. package/dist/chunk-XKQMCY5Z.cjs.map +1 -0
  48. package/dist/cli.cjs +273 -76
  49. package/dist/cli.cjs.map +1 -1
  50. package/dist/cli.js +269 -73
  51. package/dist/cli.js.map +1 -1
  52. package/dist/headless.cjs +8 -1880
  53. package/dist/headless.cjs.map +1 -1
  54. package/dist/headless.d.cts +3 -1
  55. package/dist/headless.d.ts +3 -1
  56. package/dist/headless.js +25 -1886
  57. package/dist/headless.js.map +1 -1
  58. package/dist/index-BIdnqtW8.d.ts +234 -0
  59. package/dist/index-Brk_uHs6.d.cts +234 -0
  60. package/dist/{index-FVXXKf2v.d.ts → index-CPMlaQsC.d.ts} +76 -153
  61. package/dist/{index-FVXXKf2v.d.cts → index-DHINe-rA.d.cts} +76 -153
  62. package/dist/index.cjs +64 -532
  63. package/dist/index.cjs.map +1 -1
  64. package/dist/index.d.cts +7 -4
  65. package/dist/index.d.ts +7 -4
  66. package/dist/index.js +63 -504
  67. package/dist/index.js.map +1 -1
  68. package/dist/{memoryAdapter-CBu7_3Pp.d.ts → memoryAdapter-Ck0ccZbF.d.cts} +3 -2
  69. package/dist/{memoryAdapter-BJjyLR-1.d.cts → memoryAdapter-DxEKaG0b.d.ts} +3 -2
  70. package/dist/placeholders-C4ftwaII.d.ts +5 -0
  71. package/dist/placeholders-x_3rt65M.d.cts +5 -0
  72. package/dist/react.cjs +1952 -3257
  73. package/dist/react.cjs.map +1 -1
  74. package/dist/react.d.cts +188 -50
  75. package/dist/react.d.ts +188 -50
  76. package/dist/react.js +1763 -3013
  77. package/dist/react.js.map +1 -1
  78. package/dist/server-handler.cjs +14 -0
  79. package/dist/server-handler.cjs.map +1 -0
  80. package/dist/server-handler.d.cts +3 -0
  81. package/dist/server-handler.d.ts +3 -0
  82. package/dist/server-handler.js +14 -0
  83. package/dist/server-handler.js.map +1 -0
  84. package/dist/server.cjs +1096 -532
  85. package/dist/server.cjs.map +1 -1
  86. package/dist/server.d.cts +77 -154
  87. package/dist/server.d.ts +77 -154
  88. package/dist/server.js +1050 -448
  89. package/dist/server.js.map +1 -1
  90. package/dist/storage.cjs +239 -232
  91. package/dist/storage.cjs.map +1 -1
  92. package/dist/storage.d.cts +21 -5
  93. package/dist/storage.d.ts +21 -5
  94. package/dist/storage.js +227 -207
  95. package/dist/storage.js.map +1 -1
  96. package/dist/styles/animated-image.css +112 -0
  97. package/dist/styles/dropzone-and-tiles.css +247 -0
  98. package/dist/styles/modals-and-dock.css +248 -0
  99. package/dist/styles/primitives.css +197 -0
  100. package/dist/styles/quality-and-inputs.css +184 -0
  101. package/dist/styles/tokens.css +123 -0
  102. package/dist/styles.css +235 -220
  103. package/dist/types-BrP-o9nd.d.cts +85 -0
  104. package/dist/types-sZ3KoFQY.d.ts +85 -0
  105. package/dist/types-yShGNQnW.d.cts +61 -0
  106. package/dist/types-yShGNQnW.d.ts +61 -0
  107. package/dist/{types-CiLY0DwB.d.cts → upload-C5Trd6Dh.d.cts} +27 -53
  108. package/dist/{types-CiLY0DwB.d.ts → upload-C5Trd6Dh.d.ts} +27 -53
  109. package/package.json +92 -32
  110. package/dist/blurService-DqsnfPTd.d.cts +0 -172
  111. package/dist/blurService-DqsnfPTd.d.ts +0 -172
package/README.md CHANGED
@@ -1,259 +1,243 @@
1
- # img-upload-kit
2
-
3
- Production-grade, zero-lock-in image upload and processing framework with **Sharp AVIF/WebP optimization**, **smart size-guarding**, **WebP LQIP & BlurHash placeholders**, **4-edge dominant color extraction**, **pluggable storage adapters**, **4 styling tiers**, and the **`<AnimatedImage />` blur-up display component** for React & Next.js.
4
-
5
- [![Live Interactive Demo](https://img.shields.io/badge/Live%20Demo-upload--kit.buildwithsherbo.com-0284c7?style=for-the-badge)](https://upload-kit.buildwithsherbo.com)
6
- [![npm version](https://img.shields.io/npm/v/img-upload-kit?color=38bdf8&style=for-the-badge)](https://www.npmjs.com/package/img-upload-kit)
7
- [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg?style=for-the-badge)](./LICENSE)
8
- [![Studio Repo](https://img.shields.io/badge/Studio%20Code-image--upload--kit--studio-1e293b?style=for-the-badge&logo=github)](https://github.com/MoElsherbiny/image-upload-kit-studio)
9
-
10
- ---
11
-
12
- ## Interactive Studio & Live Demo
13
-
14
- Experience the full pipeline live in your browser:
15
-
16
- 👉 **[upload-kit.buildwithsherbo.com](https://upload-kit.buildwithsherbo.com)**
17
-
18
- - **Watch Code Stream Live**: Synchronized Shiki IDE writing `<AnimatedImage />`, upload route, and UI code while the live preview mutates at 60 FPS.
19
- - **4-Layer Placeholder Lab**: Side-by-side comparison of **4-Edge Ambient Color** (0 B runtime), **Client BlurHash** (~28 chars), **WebP LQIP Data URL** (<1 KB), and the final **Sharp AVIF/WebP** asset.
20
- - **Sharp Size-Guard Sandbox**: Drop any image to inspect real-time byte savings, dimensions, and color extraction.
21
- - **Studio Source Code**: Open source at [`MoElsherbiny/image-upload-kit-studio`](https://github.com/MoElsherbiny/image-upload-kit-studio).
22
-
23
- ---
24
-
25
- ## The Problems Solved
26
-
27
- 1. **Pipeline Fragmentation**: Eliminates stitching separate libraries for file validation, resizing, AVIF/WebP conversion, placeholder generation, storage uploading, and UI forms. Provides one unified pipeline from browser dropzone to CDN.
28
- 2. **AVIF Bloat on Pre-compressed JPEGs**: Naive AVIF converters expand pre-compressed inputs by 30–80%. Our **Sharp Size-Guard** evaluates output bytes against source size, scales quality proportionally, and falls back to WebP or passthrough.
29
- 3. **Cumulative Layout Shift (CLS)**: Eliminates layout shifts by generating instant **WebP LQIP data URLs** (<8KB), client-decoded **BlurHash** strings, and **4-edge ambient colors** before full-resolution assets load.
30
- 4. **Storage Vendor Lock-in**: Decouples application logic from cloud vendors via a uniform `StorageAdapter` contract across AWS S3, Cloudflare R2, Supabase, Local Filesystem, or In-Memory (testing).
31
- 5. **UI Styling Lock-in**: Provides 4 styling tiers: default `--iuk-*` CSS tokens, `unstyled` mode with Tailwind/CSS Modules, partial slot renderers, or 100% headless hooks and primitives.
32
-
33
- ---
34
-
35
- ## Installation
36
-
37
- ```bash
38
- # npm
39
- npm install img-upload-kit
40
- # pnpm
41
- pnpm add img-upload-kit
42
- # yarn
43
- yarn add img-upload-kit
44
- # bun
45
- bun add img-upload-kit
46
-
47
- # Optional peer dependencies (only if using React or Headless UI):
48
- npm install react react-dom
49
- ```
50
-
51
- > **Requirements**: Node.js `>= 18.17.0` on the server for `sharp` and Web Fetch `Request`/`FormData` APIs. React `>= 18.0.0` for UI components.
52
-
53
- ---
54
-
55
- ## Subpath Exports Reference
56
-
57
- | Subpath | Environment | Key Exports |
58
- | --- | --- | --- |
59
- | `img-upload-kit` | Universal | `STANDARD_PRESETS`, `defineUploadPresets`, types, color utils, validators, `createMemoryStorageAdapter`, `decodeBlurHashToDataURL`, `getBlurData` |
60
- | `img-upload-kit/server` | Node.js | `createImageUploader`, `createUploadHttpHandler`, `optimizeImage`, `generateBlurPlaceholder` |
61
- | `img-upload-kit/storage` | Node / Edge | `createStorageAdapter`, `createS3StorageAdapter`, `createSupabaseStorageAdapter`, `createLocalFsStorageAdapter` |
62
- | `img-upload-kit/blur` | Universal | `BlurService`, `createBlurService`, `decodeBlurHashToDataURL`, `LRUCache` |
63
- | `img-upload-kit/react` | React Client | `<ImageUploadProvider>`, `<SingleImageUpload>`, `<MultiImageUpload>`, `<AnimatedImage>`, `<BlurImage>` |
64
- | `img-upload-kit/headless` | React Client | `<HeadlessSingleUpload>`, `<HeadlessMultiUpload>`, `useSingleUpload`, `useMultiUpload`, `useDropzone` |
65
- | `img-upload-kit/cli` | Node CLI | CLI binaries (`img-upload-kit` / `blur-cli`) for build-time manifest generation |
66
- | `img-upload-kit/styles.css` | CSS | Complete stylesheet powered by `--iuk-*` design tokens (supports dark mode & RTL) |
67
-
68
- ---
69
-
70
- ## Universal Presets Reference
71
-
72
- Built-in visual role presets balance dimension constraints, compression quality, and blur generation:
73
-
74
- | Preset | Max Dimensions | Max Size | Quality | Allowed Formats | Blur / Notes |
75
- | --- | --- | --- | --- | --- | --- |
76
- | `thumbnail` | `600x600` | 5 MB | Q78 | JPEG, PNG, WebP, AVIF | Medium quality, compact size |
77
- | `avatar` | `512x512` | 5 MB | Q80 | JPEG, PNG, WebP, AVIF | Medium quality, square profile |
78
- | `card` | `1200x900` | 8 MB | Q85 | JPEG, PNG, WebP, AVIF, SVG | High quality content cards |
79
- | `cover` | `1920x1080` | 10 MB | Q88 | JPEG, PNG, WebP, AVIF, SVG | High quality wide banners |
80
- | `hero` | `2560x1440` | 15 MB | Q92 | JPEG, PNG, WebP, AVIF, SVG | Maximum detail for hero headers |
81
- | `logo` | `800x800` | 5 MB | Q90 | JPEG, PNG, WebP, AVIF, SVG | Vector SVG passthrough & sharp edges |
82
- | `original` | `4096x4096` | 25 MB | Q95 | JPEG, PNG, WebP, AVIF, SVG | Preserves maximum master fidelity |
83
-
84
- ```ts
85
- import { defineUploadPresets } from 'img-upload-kit';
86
-
87
- export const presets = defineUploadPresets({
88
- 'product-gallery': {
89
- folder: 'catalog/products',
90
- maxDimensions: { width: 1600, height: 1600 },
91
- maxSizeMB: 10,
92
- quality: 85,
93
- includeBlurHash: true,
94
- },
95
- });
96
- ```
97
-
98
- ---
99
-
100
- ## Subpath Usage Guides
101
-
102
- ### 1. Server Uploader & Route Handler (`/server` & `/storage`)
103
-
104
- ```ts
105
- // app/api/upload/route.ts (Next.js App Router, Remix, Hono, SvelteKit)
106
- import { createImageUploader, createUploadHttpHandler } from 'img-upload-kit/server';
107
- import { createS3StorageAdapter } from 'img-upload-kit/storage';
108
-
109
- const storage = createS3StorageAdapter({
110
- publicBaseUrl: 'https://cdn.example.com',
111
- defaultBucket: 'media',
112
- putObject: (input) => s3.send(new PutObjectCommand(input)),
113
- deleteObject: (input) => s3.send(new DeleteObjectCommand(input)),
114
- });
115
-
116
- export const uploader = createImageUploader({ storage, defaultBucket: 'media' });
117
- export const { POST, DELETE } = createUploadHttpHandler({ uploader });
118
- ```
119
-
120
- ### 2. Client Provider & Upload UI (`/react` & `/styles.css`)
121
-
122
- ```tsx
123
- import { useState } from 'react';
124
- import { ImageUploadProvider, SingleImageUpload, MultiImageUpload } from 'img-upload-kit/react';
125
- import type { UploadedImageData } from 'img-upload-kit';
126
- import 'img-upload-kit/styles.css';
127
-
128
- export function MediaForm() {
129
- const [avatar, setAvatar] = useState<UploadedImageData | null>(null);
130
- const [gallery, setGallery] = useState<UploadedImageData[]>([]);
131
-
132
- return (
133
- <ImageUploadProvider endpoint="/api/upload" locale="en">
134
- <SingleImageUpload preset="avatar" label="Avatar" value={avatar} onChange={setAvatar} />
135
- <MultiImageUpload preset="card" label="Gallery" value={gallery} onChange={setGallery} maxImages={10} />
136
- </ImageUploadProvider>
137
- );
138
- }
139
- ```
140
-
141
- ### 3. Build-Time Blur Manifests & Runtime Lookup (`/cli` & `/blur`)
142
-
143
- ```bash
144
- npx img-upload-kit generate --dir ./public/images --out ./src/blur-manifest.json
145
- ```
146
- ```ts
147
- import { createBlurService } from 'img-upload-kit/blur';
148
- import manifest from './blur-manifest.json';
149
-
150
- const blurService = createBlurService({ manifest });
151
- const { color, blur: blurDataUrl } = blurService.getProgressiveData('/images/hero.avif');
152
- ```
153
-
154
- ---
155
-
156
- ## The 4 Styling Tiers
157
-
158
- Customize UI components cleanly at any level of abstraction:
159
-
160
- ### Tier 1: CSS Variables (`--iuk-*`) & Automatic Dark Mode
161
- ```css
162
- :root {
163
- --iuk-primary: #4f46e5;
164
- --iuk-primary-hover: #4338ca;
165
- --iuk-radius-md: 14px;
166
- }
167
- .dark { --iuk-primary: #818cf8; --iuk-surface: #0f172a; }
168
- ```
169
-
170
- ### Tier 2: Unstyled Mode with `classNames` & `data-[state=*]` (Tailwind CSS)
171
- ```tsx
172
- <SingleImageUpload
173
- unstyled value={avatar} onChange={setAvatar}
174
- classNames={{
175
- dropzone: 'rounded-2xl border-2 border-dashed p-6 text-center data-[state=dragging]:border-indigo-500',
176
- browseButton: 'rounded-lg bg-indigo-600 px-4 py-2 text-white',
177
- }}
178
- />
179
- ```
180
-
181
- ### Tier 3: Partial Slot Renderers
182
- ```tsx
183
- <SingleImageUpload
184
- value={avatar} onChange={setAvatar}
185
- renderDropzone={(ctx) => (
186
- <div {...ctx.dropzoneProps} className="p-8 border border-dashed rounded-xl">
187
- <button type="button" onClick={ctx.openFileDialog}>Select Photo</button>
188
- </div>
189
- )}
190
- />
191
- ```
192
-
193
- ### Tier 4: Headless Hooks & Primitives (`/headless`)
194
- ```tsx
195
- import { HeadlessSingleUpload, useDropzone } from 'img-upload-kit/headless';
196
-
197
- <HeadlessSingleUpload preset="avatar" value={avatar} onChange={setAvatar}>
198
- {(ctrl) => (
199
- <div {...ctrl.getDropzoneProps()}>
200
- <input {...ctrl.getInputProps()} />
201
- {ctrl.preview ? <img src={ctrl.preview} alt="" /> : <button type="button" onClick={ctrl.openFileDialog}>Upload</button>}
202
- </div>
203
- )}
204
- </HeadlessSingleUpload>
205
- ```
206
-
207
- ---
208
-
209
- ## AnimatedImage (Blur-Up Display Component)
210
-
211
- `<AnimatedImage />` provides an instant blur-up placeholder crossfade to full resolution without layout shift:
212
-
213
- ```tsx
214
- import { AnimatedImage } from 'img-upload-kit/react';
215
- import 'img-upload-kit/styles.css';
216
-
217
- // 1. Direct shorthand with UploadedImageData:
218
- <AnimatedImage image={img} showShimmer />
219
-
220
- // 2. Explicit props in standard React:
221
- <AnimatedImage
222
- src={img.url}
223
- alt={img.altText || 'Product'}
224
- blurDataUrl={img.blurDataUrl}
225
- blurHash={img.blurHash}
226
- blurColor={img.blurColor}
227
- width={800}
228
- height={600}
229
- showShimmer
230
- />
231
-
232
- // 3. Next.js App Router (as={Image}):
233
- import Image from 'next/image';
234
- <div style={{ position: 'relative', aspectRatio: '16/9' }}>
235
- <AnimatedImage image={img} as={Image} fill sizes="100vw" imageComponentProps={{ priority: true, quality: 80 }} />
236
- </div>
237
- ```
238
-
239
- ---
240
-
241
- ## Documentation
242
-
243
- - [AI Agent Contributor Guide (`AGENTS.md`)](./AGENTS.md) — Architecture, guardrails, and styling tiers for AI agents.
244
- - [Custom Styling & Headless Guide (`docs/CUSTOM_STYLING.md`)](./docs/CUSTOM_STYLING.md) — Theming, slots, and headless recipes.
245
- - [AnimatedImage & Blur-Up Guide (`docs/ANIMATED_IMAGE.md`)](./docs/ANIMATED_IMAGE.md) — Progressive reveal component architecture.
246
- - [Architecture & Internals (`docs/ARCHITECTURE.md`)](./docs/ARCHITECTURE.md) — Sharp size-guarding and 4-edge color math.
247
- - [Framework & Storage Recipes (`docs/FRAMEWORK_RECIPES.md`)](./docs/FRAMEWORK_RECIPES.md) — Next.js, Hono, Remix, S3, Supabase recipes.
248
- - [Interactive Studio Code](https://github.com/MoElsherbiny/image-upload-kit-studio) — Source for the live demo playground.
249
- - [Contributing Guide (`CONTRIBUTING.md`)](./CONTRIBUTING.md) & [Changelog (`CHANGELOG.md`)](./CHANGELOG.md).
250
-
251
- ## License & Author
252
-
253
- **Author & Identity**: [buildwithsherbo](https://www.buildwithsherbo.com/) — Mohamed Elsherbiny
254
- - **Website**: [buildwithsherbo.com](https://www.buildwithsherbo.com/)
255
- - **Live Demo**: [upload-kit.buildwithsherbo.com](https://upload-kit.buildwithsherbo.com)
256
- - **GitHub**: [@MoElsherbiny](https://github.com/MoElsherbiny)
257
- - **Email**: [mo@buildwithsherbo.com](mailto:mo@buildwithsherbo.com)
258
-
259
- Distributed under the [MIT License](./LICENSE).
1
+ # img-upload-kit
2
+
3
+ Production-grade, zero-lock-in image upload and processing framework with **Sharp AVIF/WebP optimization**, **smart size-guarding**, **responsive `srcSet` variants**, **WebP LQIP & BlurHash placeholders**, **4-edge dominant color extraction**, **pluggable storage adapters**, **4 styling tiers**, and the **`<AnimatedImage />` blur-up display component** for React & Next.js.
4
+
5
+ [![Live Interactive Demo](https://img.shields.io/badge/Live%20Demo-upload--kit.buildwithsherbo.com-0284c7?style=for-the-badge)](https://upload-kit.buildwithsherbo.com)
6
+ [![npm version](https://img.shields.io/npm/v/img-upload-kit?color=38bdf8&style=for-the-badge)](https://www.npmjs.com/package/img-upload-kit)
7
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg?style=for-the-badge)](./LICENSE)
8
+ [![Studio Repo](https://img.shields.io/badge/Studio%20Code-image--upload--kit--studio-1e293b?style=for-the-badge&logo=github)](https://github.com/MoElsherbiny/image-upload-kit-studio)
9
+
10
+ ---
11
+
12
+ ## Interactive Studio & Live Demo
13
+
14
+ Experience the full pipeline live in your browser: 👉 **[upload-kit.buildwithsherbo.com](https://upload-kit.buildwithsherbo.com)**
15
+
16
+ - **Watch Code Stream Live**: Synchronized Shiki IDE writing `<AnimatedImage />`, upload route, and UI code while the live preview mutates at 60 FPS.
17
+ - **4-Layer Placeholder Lab**: Side-by-side comparison of **4-Edge Ambient Color** (0 B runtime), **Client BlurHash** (~28 chars), **WebP LQIP Data URL** (<1 KB), and the final **Sharp AVIF/WebP** asset.
18
+ - **Sharp Size-Guard Sandbox**: Drop or paste any image to inspect real-time byte savings, responsive `srcSet` variants, dimensions, and color extraction.
19
+ - **Upgrading from v1.x?** Read the [**v2.0 Migration Guide (`MIGRATION.md`)**](./MIGRATION.md).
20
+
21
+ ---
22
+
23
+ ## Installation
24
+
25
+ ```bash
26
+ # Client / React UI only (zero native dependencies):
27
+ npm install img-upload-kit react react-dom
28
+
29
+ # Server / Full-Stack (Next.js, Remix, Hono, SvelteKit, Node) or CLI:
30
+ npm install img-upload-kit sharp lqip-modern
31
+
32
+ # pnpm / yarn / bun:
33
+ pnpm add img-upload-kit sharp lqip-modern
34
+ ```
35
+
36
+ > **Requirements**: Node.js `>= 20.0.0` on the server for `sharp` and Web Fetch `Request`/`FormData` APIs. React `>= 18.0.0` for UI components (`lucide-react` is no longer required — all icons are inlined).
37
+
38
+ ---
39
+
40
+ ## Subpath Exports Reference
41
+
42
+ | Subpath | Environment | Key Exports |
43
+ | --- | --- | --- |
44
+ | `img-upload-kit` | Universal | `STANDARD_PRESETS`, `defineUploadPresets`, types, color utils, validators, `createMemoryStorageAdapter`, `decodeBlurHashToDataURL`, `getBlurData` |
45
+ | `img-upload-kit/server` | Node.js | `createImageUploader`, `createUploadHttpHandler`, `optimizeImage`, `generateBlurPlaceholder`, `sniffImageFormat`, `sanitizeSvg` |
46
+ | `img-upload-kit/server/handler` | Node / Edge | Edge-safe `createUploadHttpHandler` (zero Sharp or `node:fs` imports at module load) |
47
+ | `img-upload-kit/storage` | Node / Edge | `createStorageAdapter`, `createS3StorageAdapter`, `createSupabaseStorageAdapter`, `createLocalFsStorageAdapter` |
48
+ | `img-upload-kit/blur` | Universal | `BlurService`, `createBlurService`, `decodeBlurHashToDataURL`, `LRUCache` |
49
+ | `img-upload-kit/react` | React Client | `<ImageUploadProvider>`, `<SingleImageUpload>`, `<MultiImageUpload>`, `<AnimatedImage>`, `<BlurImage>` |
50
+ | `img-upload-kit/headless` | React Client | `<HeadlessSingleUpload>`, `<HeadlessMultiUpload>`, `useSingleUpload`, `useMultiUpload`, `useDropzone` |
51
+ | `img-upload-kit/cli` | Node CLI | CLI binaries (`img-upload-kit` / `blur-cli`) for build-time manifest generation |
52
+ | `img-upload-kit/styles.css` | CSS | Complete stylesheet (`--iuk-*` tokens, dark mode, RTL) + per-layer `img-upload-kit/styles/*.css` |
53
+
54
+ ---
55
+
56
+ ## Universal Presets & Responsive Variants (`variantWidths`)
57
+
58
+ Built-in visual role presets balance dimension constraints, compression quality, and blur generation. By default, only raster formats (`JPEG, PNG, WebP, AVIF`) are accepted and verified via binary magic-byte sniffing; opt into sanitized SVG support per preset or uploader with `allowSvg: true`.
59
+
60
+ | Preset | Max Dimensions | Max Size | Quality | Default Formats | Notes |
61
+ | --- | --- | --- | --- | --- | --- |
62
+ | `thumbnail` | `600x600` | 5 MB | Q78 | JPEG, PNG, WebP, AVIF | Compact grid thumbnails |
63
+ | `avatar` | `512x512` | 5 MB | Q80 | JPEG, PNG, WebP, AVIF | Square user profiles |
64
+ | `card` | `1200x900` | 8 MB | Q85 | JPEG, PNG, WebP, AVIF | Content & product cards |
65
+ | `cover` | `1920x1080` | 10 MB | Q88 | JPEG, PNG, WebP, AVIF | Wide section banners |
66
+ | `hero` | `2560x1440` | 15 MB | Q92 | JPEG, PNG, WebP, AVIF | Full-bleed hero headers |
67
+ | `logo` | `800x800` | 5 MB | Q90 | JPEG, PNG, WebP, AVIF | Set `allowSvg: true` for sanitized vector SVGs |
68
+ | `original` | `4096x4096` | 25 MB | Q95 | JPEG, PNG, WebP, AVIF | Master archival fidelity |
69
+
70
+ ```ts
71
+ import { defineUploadPresets } from 'img-upload-kit';
72
+
73
+ export const presets = defineUploadPresets({
74
+ hero: {
75
+ folder: 'banners/hero',
76
+ maxDimensions: { width: 2560, height: 1440 },
77
+ variantWidths: [640, 1024, 1920], // Encodes <base>-640w.avif, etc. + populates data.srcSet
78
+ maxSizeMB: 15,
79
+ quality: 90,
80
+ includeBlurHash: true,
81
+ },
82
+ logo: {
83
+ folder: 'brand/logos',
84
+ maxDimensions: { width: 800, height: 800 },
85
+ allowSvg: true, // Runs built-in allowlist SVG sanitizer before storage
86
+ },
87
+ });
88
+ ```
89
+
90
+ ---
91
+
92
+ ## Server Uploader & Secure-by-Default HTTP Handler
93
+
94
+ `createUploadHttpHandler` enforces binary magic-byte sniffing, streaming `maxBodyBytes` limits (HTTP `413`), `allowedOrigins` CSRF checks (HTTP `403`), bucket allowlists on `DELETE`, and generic `500` responses with `onError`. Client `FormData` overrides are ignored unless explicitly enabled in `allowClientOverrides`:
95
+
96
+ ```ts
97
+ // app/api/upload/route.ts (Next.js App Router, Remix, Hono, SvelteKit)
98
+ import { createImageUploader, createUploadHttpHandler } from 'img-upload-kit/server';
99
+ import { createS3StorageAdapter } from 'img-upload-kit/storage';
100
+ import { presets } from '@/lib/upload-presets';
101
+
102
+ const storage = createS3StorageAdapter({
103
+ publicBaseUrl: 'https://cdn.example.com',
104
+ defaultBucket: 'media',
105
+ putObject: (input) => s3.send(new PutObjectCommand(input)),
106
+ deleteObject: (input) => s3.send(new DeleteObjectCommand(input)),
107
+ });
108
+
109
+ export const uploader = createImageUploader({ storage, defaultBucket: 'media', presets });
110
+
111
+ export const { POST, DELETE } = createUploadHttpHandler({
112
+ uploader,
113
+ maxBodyBytes: 16 * 1024 * 1024,
114
+ allowedOrigins: ['https://app.example.com'],
115
+ authorize: async ({ action, request, preset, bucket, path }) => {
116
+ const user = await verifySession(request);
117
+ if (!user) return false;
118
+ if (action === 'delete') return user.role === 'admin' && bucket === 'media';
119
+ return true;
120
+ },
121
+ allowClientOverrides: {
122
+ filename: true, // Sanitized, forced to output extension, never overwrites existing files
123
+ folder: (folder) => (folder.startsWith('users/') ? folder : null),
124
+ dimensions: true, // Clamped to preset maxDimensions
125
+ format: true,
126
+ quality: true,
127
+ },
128
+ onError: (error, ctx) => console.error(`[upload:${ctx.action}]`, error),
129
+ });
130
+ ```
131
+
132
+ ---
133
+
134
+ ## React Upload UI: Paste, Camera Capture, Cancel/Retry & Forms
135
+
136
+ `<SingleImageUpload>` and `<MultiImageUpload>` support **clipboard image paste** (`Ctrl+V` / `Cmd+V`), **mobile camera capture** (`capture="environment"`), **per-tile cancel & retry** (backed by per-upload `AbortController`), and **native HTML form / React Hook Form integration** (`name`, `formValue`, `ref`, `required`, `minImages`, `onBlur`):
137
+
138
+ ```tsx
139
+ import { useRef, useState } from 'react';
140
+ import {
141
+ ImageUploadProvider,
142
+ SingleImageUpload,
143
+ MultiImageUpload,
144
+ type ImageUploadHandle,
145
+ } from 'img-upload-kit/react';
146
+ import type { UploadedImageData } from 'img-upload-kit';
147
+ import 'img-upload-kit/styles.css';
148
+
149
+ export function ProductMediaForm() {
150
+ const uploadRef = useRef<ImageUploadHandle>(null);
151
+ const [cover, setCover] = useState<UploadedImageData | null>(null);
152
+ const [gallery, setGallery] = useState<UploadedImageData[]>([]);
153
+
154
+ return (
155
+ <ImageUploadProvider endpoint="/api/upload" locale="en">
156
+ <form onSubmit={(e) => { e.preventDefault(); /* FormData includes coverUrl & galleryJson */ }}>
157
+ <SingleImageUpload
158
+ ref={uploadRef}
159
+ preset="cover"
160
+ label="Cover Photo"
161
+ name="coverUrl"
162
+ formValue="url"
163
+ required
164
+ enablePaste
165
+ pasteTarget="document"
166
+ capture="environment"
167
+ value={cover}
168
+ onChange={setCover}
169
+ />
170
+ <MultiImageUpload
171
+ mode="multiple"
172
+ preset="card"
173
+ label="Product Gallery"
174
+ name="galleryJson"
175
+ formValue="json"
176
+ required
177
+ minImages={2}
178
+ maxImages={10}
179
+ value={gallery}
180
+ onChange={setGallery}
181
+ onUploadCancel={(id) => console.log('Cancelled tile:', id)}
182
+ />
183
+ <button type="submit">Save Product</button>
184
+ </form>
185
+ </ImageUploadProvider>
186
+ );
187
+ }
188
+ ```
189
+
190
+ ---
191
+
192
+ ## AnimatedImage (Progressive Blur-Up & Settle Reveal)
193
+
194
+ `<AnimatedImage />` delivers a 60 FPS settle reveal (`opacity: 0 -> 1` + `scale(1.02 -> 1)` over `600ms ease-in-out`), pre-decodes via `img.decode()` before crossfading, auto-wires responsive `srcSet` from `image.variants`, and preserves the blurred backdrop on load error with automatic `fallbackSrc` retry:
195
+
196
+ ```tsx
197
+ import { AnimatedImage } from 'img-upload-kit/react';
198
+ import Image from 'next/image';
199
+ import 'img-upload-kit/styles.css';
200
+
201
+ // 1. Direct shorthand with UploadedImageData (auto-wires variants srcSet, LQIP, blurColor):
202
+ <AnimatedImage image={img} showShimmer />
203
+
204
+ // 2. Next.js App Router (as={Image} + first-class priority & quality props):
205
+ <div style={{ position: 'relative', aspectRatio: '16/9' }}>
206
+ <AnimatedImage
207
+ image={img}
208
+ as={Image}
209
+ fill
210
+ priority
211
+ quality={85}
212
+ sizes="(max-width: 768px) 100vw, 1200px"
213
+ fallbackSrc="/images/fallback-hero.webp"
214
+ renderError={({ retry }) => <button type="button" onClick={retry}>Reload</button>}
215
+ />
216
+ </div>
217
+ ```
218
+
219
+ ---
220
+
221
+ ## The 4 Styling Tiers
222
+
223
+ 1. **Tier 1 — CSS Variables (`--iuk-*`)**: Automatic dark mode and RTL (`dir="rtl"`) with customizable `--iuk-primary`, `--iuk-surface`, and `--iuk-radius-*` tokens.
224
+ 2. **Tier 2 — Unstyled + Slots (`unstyled` + `classNames`)**: Strip built-in styles and target slots (`dropzone`, `tile`, `browseButton`) with Tailwind `data-[state=dragging|uploading|error]:*` selectors.
225
+ 3. **Tier 3 — Partial Slot Renderers**: Override specific regions via `renderDropzone`, `renderPreview`, `renderTile`, `renderHeader`, or `renderEmptyDropzone`.
226
+ 4. **Tier 4 — Headless Primitives (`img-upload-kit/headless`)**: Full control via `<HeadlessSingleUpload>`, `<HeadlessMultiUpload>`, `useSingleUpload`, and `useMultiUpload` (including `cancel(id)`, `retry(id)`, `retryAll()`, and clipboard paste).
227
+
228
+ ---
229
+
230
+ ## Documentation & Links
231
+
232
+ - [v2.0 Migration Guide (`MIGRATION.md`)](./MIGRATION.md) & [Post-v2.0 Roadmap (`ROADMAP.md`)](./ROADMAP.md)
233
+ - [AI Agent Contributor Guide (`AGENTS.md`)](./AGENTS.md)
234
+ - [Custom Styling & Headless Guide (`docs/CUSTOM_STYLING.md`)](./docs/CUSTOM_STYLING.md)
235
+ - [AnimatedImage & Blur-Up Guide (`docs/ANIMATED_IMAGE.md`)](./docs/ANIMATED_IMAGE.md)
236
+ - [Architecture & Internals (`docs/ARCHITECTURE.md`)](./docs/ARCHITECTURE.md)
237
+ - [Framework & Storage Recipes (`docs/FRAMEWORK_RECIPES.md`)](./docs/FRAMEWORK_RECIPES.md)
238
+ - [Contributing Guide (`CONTRIBUTING.md`)](./CONTRIBUTING.md) & [Changelog (`CHANGELOG.md`)](./CHANGELOG.md)
239
+
240
+ ## License & Author
241
+
242
+ **Author & Identity**: [buildwithsherbo](https://www.buildwithsherbo.com/) — Mohamed Elsherbiny ([@MoElsherbiny](https://github.com/MoElsherbiny) · [mo@buildwithsherbo.com](mailto:mo@buildwithsherbo.com))
243
+ Distributed under the [MIT License](./LICENSE).