img-upload-kit 2.0.0 → 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 (78) hide show
  1. package/CHANGELOG.md +89 -89
  2. package/MIGRATION.md +192 -192
  3. package/README.md +243 -243
  4. package/dist/bin.js +0 -0
  5. package/dist/bin.js.map +1 -1
  6. package/dist/blur.cjs +3 -3
  7. package/dist/blur.cjs.map +1 -1
  8. package/dist/blur.js +2 -2
  9. package/dist/{chunk-G7LLSKBV.js → chunk-25GYKQ6J.js} +1 -1
  10. package/dist/chunk-25GYKQ6J.js.map +1 -0
  11. package/dist/{chunk-NEDBIIE3.cjs → chunk-34L4TEE3.cjs} +1 -1
  12. package/dist/chunk-34L4TEE3.cjs.map +1 -0
  13. package/dist/{chunk-KJVYWKIG.cjs → chunk-DFCSN3X7.cjs} +1 -1
  14. package/dist/chunk-DFCSN3X7.cjs.map +1 -0
  15. package/dist/{chunk-IB3TEDLR.js → chunk-EWVIL57J.js} +1 -1
  16. package/dist/chunk-EWVIL57J.js.map +1 -0
  17. package/dist/{chunk-3ICG2SLL.cjs → chunk-FP5YIWG5.cjs} +1 -1
  18. package/dist/chunk-FP5YIWG5.cjs.map +1 -0
  19. package/dist/{chunk-TT5C7K3Q.js → chunk-HILELQGS.js} +1 -1
  20. package/dist/chunk-HILELQGS.js.map +1 -0
  21. package/dist/{chunk-AI3JJLSA.js → chunk-IDLLCVGB.js} +3 -3
  22. package/dist/chunk-IDLLCVGB.js.map +1 -0
  23. package/dist/{chunk-4ASFG3KB.js → chunk-J6A44Y3W.js} +1 -1
  24. package/dist/chunk-J6A44Y3W.js.map +1 -0
  25. package/dist/{chunk-YRXXQUDE.cjs → chunk-LOSVFO56.cjs} +1 -1
  26. package/dist/chunk-LOSVFO56.cjs.map +1 -0
  27. package/dist/{chunk-WISG74VL.cjs → chunk-MBQJ6NVJ.cjs} +1 -1
  28. package/dist/chunk-MBQJ6NVJ.cjs.map +1 -0
  29. package/dist/{chunk-CV23O4YU.js → chunk-MHGQEBPW.js} +1 -1
  30. package/dist/chunk-MHGQEBPW.js.map +1 -0
  31. package/dist/chunk-QHU7FUO4.cjs.map +1 -1
  32. package/dist/{chunk-U2TWTEBY.cjs → chunk-TA5Y64JM.cjs} +24 -24
  33. package/dist/chunk-TA5Y64JM.cjs.map +1 -0
  34. package/dist/{chunk-QQHZJKNA.js → chunk-VYVZVUNI.js} +1 -1
  35. package/dist/chunk-VYVZVUNI.js.map +1 -0
  36. package/dist/{chunk-2OVNXHTN.cjs → chunk-XKQMCY5Z.cjs} +1 -1
  37. package/dist/chunk-XKQMCY5Z.cjs.map +1 -0
  38. package/dist/cli.cjs.map +1 -1
  39. package/dist/cli.js.map +1 -1
  40. package/dist/headless.cjs +4 -4
  41. package/dist/headless.cjs.map +1 -1
  42. package/dist/headless.js +3 -3
  43. package/dist/index.cjs +5 -5
  44. package/dist/index.cjs.map +1 -1
  45. package/dist/index.js +4 -4
  46. package/dist/react.cjs +138 -138
  47. package/dist/react.cjs.map +1 -1
  48. package/dist/react.js +5 -5
  49. package/dist/react.js.map +1 -1
  50. package/dist/server-handler.cjs +2 -2
  51. package/dist/server-handler.cjs.map +1 -1
  52. package/dist/server-handler.js +1 -1
  53. package/dist/server.cjs +30 -30
  54. package/dist/server.cjs.map +1 -1
  55. package/dist/server.js +2 -2
  56. package/dist/server.js.map +1 -1
  57. package/dist/storage.cjs +9 -9
  58. package/dist/storage.cjs.map +1 -1
  59. package/dist/storage.js +1 -1
  60. package/dist/storage.js.map +1 -1
  61. package/dist/styles/animated-image.css +112 -112
  62. package/dist/styles/tokens.css +123 -123
  63. package/dist/styles.css +235 -235
  64. package/package.json +3 -3
  65. package/dist/chunk-2OVNXHTN.cjs.map +0 -1
  66. package/dist/chunk-3ICG2SLL.cjs.map +0 -1
  67. package/dist/chunk-4ASFG3KB.js.map +0 -1
  68. package/dist/chunk-AI3JJLSA.js.map +0 -1
  69. package/dist/chunk-CV23O4YU.js.map +0 -1
  70. package/dist/chunk-G7LLSKBV.js.map +0 -1
  71. package/dist/chunk-IB3TEDLR.js.map +0 -1
  72. package/dist/chunk-KJVYWKIG.cjs.map +0 -1
  73. package/dist/chunk-NEDBIIE3.cjs.map +0 -1
  74. package/dist/chunk-QQHZJKNA.js.map +0 -1
  75. package/dist/chunk-TT5C7K3Q.js.map +0 -1
  76. package/dist/chunk-U2TWTEBY.cjs.map +0 -1
  77. package/dist/chunk-WISG74VL.cjs.map +0 -1
  78. package/dist/chunk-YRXXQUDE.cjs.map +0 -1
package/README.md CHANGED
@@ -1,243 +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**, **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).
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).
package/dist/bin.js CHANGED
File without changes