img-upload-kit 1.1.1 → 2.0.0

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 +56 -41
  2. package/MIGRATION.md +192 -0
  3. package/README.md +138 -154
  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-2OVNXHTN.cjs +514 -0
  17. package/dist/chunk-2OVNXHTN.cjs.map +1 -0
  18. package/dist/chunk-3ICG2SLL.cjs +135 -0
  19. package/dist/chunk-3ICG2SLL.cjs.map +1 -0
  20. package/dist/chunk-4ASFG3KB.js +514 -0
  21. package/dist/chunk-4ASFG3KB.js.map +1 -0
  22. package/dist/chunk-AI3JJLSA.js +2370 -0
  23. package/dist/chunk-AI3JJLSA.js.map +1 -0
  24. package/dist/chunk-CV23O4YU.js +145 -0
  25. package/dist/chunk-CV23O4YU.js.map +1 -0
  26. package/dist/chunk-G7LLSKBV.js +158 -0
  27. package/dist/chunk-G7LLSKBV.js.map +1 -0
  28. package/dist/chunk-IB3TEDLR.js +294 -0
  29. package/dist/chunk-IB3TEDLR.js.map +1 -0
  30. package/dist/chunk-KJVYWKIG.cjs +158 -0
  31. package/dist/chunk-KJVYWKIG.cjs.map +1 -0
  32. package/dist/chunk-NEDBIIE3.cjs +226 -0
  33. package/dist/chunk-NEDBIIE3.cjs.map +1 -0
  34. package/dist/chunk-QHU7FUO4.cjs +37 -0
  35. package/dist/chunk-QHU7FUO4.cjs.map +1 -0
  36. package/dist/chunk-QQHZJKNA.js +135 -0
  37. package/dist/chunk-QQHZJKNA.js.map +1 -0
  38. package/dist/chunk-S2IFAWJK.js +37 -0
  39. package/dist/chunk-S2IFAWJK.js.map +1 -0
  40. package/dist/chunk-TT5C7K3Q.js +226 -0
  41. package/dist/chunk-TT5C7K3Q.js.map +1 -0
  42. package/dist/chunk-U2TWTEBY.cjs +2370 -0
  43. package/dist/chunk-U2TWTEBY.cjs.map +1 -0
  44. package/dist/chunk-WISG74VL.cjs +294 -0
  45. package/dist/chunk-WISG74VL.cjs.map +1 -0
  46. package/dist/chunk-YRXXQUDE.cjs +145 -0
  47. package/dist/chunk-YRXXQUDE.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 +39 -24
  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,6 +1,6 @@
1
1
  # img-upload-kit
2
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.
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
4
 
5
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
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)
@@ -11,44 +11,29 @@ Production-grade, zero-lock-in image upload and processing framework with **Shar
11
11
 
12
12
  ## Interactive Studio & Live Demo
13
13
 
14
- Experience the full pipeline live in your browser:
15
-
16
- 👉 **[upload-kit.buildwithsherbo.com](https://upload-kit.buildwithsherbo.com)**
14
+ Experience the full pipeline live in your browser: 👉 **[upload-kit.buildwithsherbo.com](https://upload-kit.buildwithsherbo.com)**
17
15
 
18
16
  - **Watch Code Stream Live**: Synchronized Shiki IDE writing `<AnimatedImage />`, upload route, and UI code while the live preview mutates at 60 FPS.
19
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.
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.
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).
32
20
 
33
21
  ---
34
22
 
35
23
  ## Installation
36
24
 
37
25
  ```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
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
49
34
  ```
50
35
 
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.
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).
52
37
 
53
38
  ---
54
39
 
@@ -57,54 +42,62 @@ npm install react react-dom
57
42
  | Subpath | Environment | Key Exports |
58
43
  | --- | --- | --- |
59
44
  | `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` |
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) |
61
47
  | `img-upload-kit/storage` | Node / Edge | `createStorageAdapter`, `createS3StorageAdapter`, `createSupabaseStorageAdapter`, `createLocalFsStorageAdapter` |
62
48
  | `img-upload-kit/blur` | Universal | `BlurService`, `createBlurService`, `decodeBlurHashToDataURL`, `LRUCache` |
63
49
  | `img-upload-kit/react` | React Client | `<ImageUploadProvider>`, `<SingleImageUpload>`, `<MultiImageUpload>`, `<AnimatedImage>`, `<BlurImage>` |
64
50
  | `img-upload-kit/headless` | React Client | `<HeadlessSingleUpload>`, `<HeadlessMultiUpload>`, `useSingleUpload`, `useMultiUpload`, `useDropzone` |
65
51
  | `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) |
52
+ | `img-upload-kit/styles.css` | CSS | Complete stylesheet (`--iuk-*` tokens, dark mode, RTL) + per-layer `img-upload-kit/styles/*.css` |
67
53
 
68
54
  ---
69
55
 
70
- ## Universal Presets Reference
56
+ ## Universal Presets & Responsive Variants (`variantWidths`)
71
57
 
72
- Built-in visual role presets balance dimension constraints, compression quality, and blur generation:
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`.
73
59
 
74
- | Preset | Max Dimensions | Max Size | Quality | Allowed Formats | Blur / Notes |
60
+ | Preset | Max Dimensions | Max Size | Quality | Default Formats | Notes |
75
61
  | --- | --- | --- | --- | --- | --- |
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 |
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 |
83
69
 
84
70
  ```ts
85
71
  import { defineUploadPresets } from 'img-upload-kit';
86
72
 
87
73
  export const presets = defineUploadPresets({
88
- 'product-gallery': {
89
- folder: 'catalog/products',
90
- maxDimensions: { width: 1600, height: 1600 },
91
- maxSizeMB: 10,
92
- quality: 85,
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,
93
80
  includeBlurHash: true,
94
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
+ },
95
87
  });
96
88
  ```
97
89
 
98
90
  ---
99
91
 
100
- ## Subpath Usage Guides
92
+ ## Server Uploader & Secure-by-Default HTTP Handler
101
93
 
102
- ### 1. Server Uploader & Route Handler (`/server` & `/storage`)
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`:
103
95
 
104
96
  ```ts
105
97
  // app/api/upload/route.ts (Next.js App Router, Remix, Hono, SvelteKit)
106
98
  import { createImageUploader, createUploadHttpHandler } from 'img-upload-kit/server';
107
99
  import { createS3StorageAdapter } from 'img-upload-kit/storage';
100
+ import { presets } from '@/lib/upload-presets';
108
101
 
109
102
  const storage = createS3StorageAdapter({
110
103
  publicBaseUrl: 'https://cdn.example.com',
@@ -113,147 +106,138 @@ const storage = createS3StorageAdapter({
113
106
  deleteObject: (input) => s3.send(new DeleteObjectCommand(input)),
114
107
  });
115
108
 
116
- export const uploader = createImageUploader({ storage, defaultBucket: 'media' });
117
- export const { POST, DELETE } = createUploadHttpHandler({ uploader });
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
+ });
118
130
  ```
119
131
 
120
- ### 2. Client Provider & Upload UI (`/react` & `/styles.css`)
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`):
121
137
 
122
138
  ```tsx
123
- import { useState } from 'react';
124
- import { ImageUploadProvider, SingleImageUpload, MultiImageUpload } from 'img-upload-kit/react';
139
+ import { useRef, useState } from 'react';
140
+ import {
141
+ ImageUploadProvider,
142
+ SingleImageUpload,
143
+ MultiImageUpload,
144
+ type ImageUploadHandle,
145
+ } from 'img-upload-kit/react';
125
146
  import type { UploadedImageData } from 'img-upload-kit';
126
147
  import 'img-upload-kit/styles.css';
127
148
 
128
- export function MediaForm() {
129
- const [avatar, setAvatar] = useState<UploadedImageData | null>(null);
149
+ export function ProductMediaForm() {
150
+ const uploadRef = useRef<ImageUploadHandle>(null);
151
+ const [cover, setCover] = useState<UploadedImageData | null>(null);
130
152
  const [gallery, setGallery] = useState<UploadedImageData[]>([]);
131
153
 
132
154
  return (
133
155
  <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} />
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>
136
185
  </ImageUploadProvider>
137
186
  );
138
187
  }
139
188
  ```
140
189
 
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
190
  ---
208
191
 
209
- ## AnimatedImage (Blur-Up Display Component)
192
+ ## AnimatedImage (Progressive Blur-Up & Settle Reveal)
210
193
 
211
- `<AnimatedImage />` provides an instant blur-up placeholder crossfade to full resolution without layout shift:
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:
212
195
 
213
196
  ```tsx
214
197
  import { AnimatedImage } from 'img-upload-kit/react';
198
+ import Image from 'next/image';
215
199
  import 'img-upload-kit/styles.css';
216
200
 
217
- // 1. Direct shorthand with UploadedImageData:
201
+ // 1. Direct shorthand with UploadedImageData (auto-wires variants srcSet, LQIP, blurColor):
218
202
  <AnimatedImage image={img} showShimmer />
219
203
 
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';
204
+ // 2. Next.js App Router (as={Image} + first-class priority & quality props):
234
205
  <div style={{ position: 'relative', aspectRatio: '16/9' }}>
235
- <AnimatedImage image={img} as={Image} fill sizes="100vw" imageComponentProps={{ priority: true, quality: 80 }} />
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
+ />
236
216
  </div>
237
217
  ```
238
218
 
239
219
  ---
240
220
 
241
- ## Documentation
221
+ ## The 4 Styling Tiers
242
222
 
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).
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).
250
227
 
251
- ## License & Author
228
+ ---
229
+
230
+ ## Documentation & Links
252
231
 
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)
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
258
241
 
242
+ **Author & Identity**: [buildwithsherbo](https://www.buildwithsherbo.com/) — Mohamed Elsherbiny ([@MoElsherbiny](https://github.com/MoElsherbiny) · [mo@buildwithsherbo.com](mailto:mo@buildwithsherbo.com))
259
243
  Distributed under the [MIT License](./LICENSE).