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.
- package/CHANGELOG.md +56 -41
- package/MIGRATION.md +192 -0
- package/README.md +138 -154
- package/dist/bin.js +1186 -0
- package/dist/bin.js.map +1 -0
- package/dist/blur.cjs +40 -412
- package/dist/blur.cjs.map +1 -1
- package/dist/blur.d.cts +5 -5
- package/dist/blur.d.ts +5 -5
- package/dist/blur.js +39 -393
- package/dist/blur.js.map +1 -1
- package/dist/blurDataUtils-DveBRtqE.d.cts +43 -0
- package/dist/blurDataUtils-yx9Q6HlU.d.ts +43 -0
- package/dist/blurService-D5TpMNji.d.ts +83 -0
- package/dist/blurService-DacwylJx.d.cts +83 -0
- package/dist/chunk-2OVNXHTN.cjs +514 -0
- package/dist/chunk-2OVNXHTN.cjs.map +1 -0
- package/dist/chunk-3ICG2SLL.cjs +135 -0
- package/dist/chunk-3ICG2SLL.cjs.map +1 -0
- package/dist/chunk-4ASFG3KB.js +514 -0
- package/dist/chunk-4ASFG3KB.js.map +1 -0
- package/dist/chunk-AI3JJLSA.js +2370 -0
- package/dist/chunk-AI3JJLSA.js.map +1 -0
- package/dist/chunk-CV23O4YU.js +145 -0
- package/dist/chunk-CV23O4YU.js.map +1 -0
- package/dist/chunk-G7LLSKBV.js +158 -0
- package/dist/chunk-G7LLSKBV.js.map +1 -0
- package/dist/chunk-IB3TEDLR.js +294 -0
- package/dist/chunk-IB3TEDLR.js.map +1 -0
- package/dist/chunk-KJVYWKIG.cjs +158 -0
- package/dist/chunk-KJVYWKIG.cjs.map +1 -0
- package/dist/chunk-NEDBIIE3.cjs +226 -0
- package/dist/chunk-NEDBIIE3.cjs.map +1 -0
- package/dist/chunk-QHU7FUO4.cjs +37 -0
- package/dist/chunk-QHU7FUO4.cjs.map +1 -0
- package/dist/chunk-QQHZJKNA.js +135 -0
- package/dist/chunk-QQHZJKNA.js.map +1 -0
- package/dist/chunk-S2IFAWJK.js +37 -0
- package/dist/chunk-S2IFAWJK.js.map +1 -0
- package/dist/chunk-TT5C7K3Q.js +226 -0
- package/dist/chunk-TT5C7K3Q.js.map +1 -0
- package/dist/chunk-U2TWTEBY.cjs +2370 -0
- package/dist/chunk-U2TWTEBY.cjs.map +1 -0
- package/dist/chunk-WISG74VL.cjs +294 -0
- package/dist/chunk-WISG74VL.cjs.map +1 -0
- package/dist/chunk-YRXXQUDE.cjs +145 -0
- package/dist/chunk-YRXXQUDE.cjs.map +1 -0
- package/dist/cli.cjs +273 -76
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +269 -73
- package/dist/cli.js.map +1 -1
- package/dist/headless.cjs +8 -1880
- package/dist/headless.cjs.map +1 -1
- package/dist/headless.d.cts +3 -1
- package/dist/headless.d.ts +3 -1
- package/dist/headless.js +25 -1886
- package/dist/headless.js.map +1 -1
- package/dist/index-BIdnqtW8.d.ts +234 -0
- package/dist/index-Brk_uHs6.d.cts +234 -0
- package/dist/{index-FVXXKf2v.d.ts → index-CPMlaQsC.d.ts} +76 -153
- package/dist/{index-FVXXKf2v.d.cts → index-DHINe-rA.d.cts} +76 -153
- package/dist/index.cjs +64 -532
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -4
- package/dist/index.d.ts +7 -4
- package/dist/index.js +63 -504
- package/dist/index.js.map +1 -1
- package/dist/{memoryAdapter-CBu7_3Pp.d.ts → memoryAdapter-Ck0ccZbF.d.cts} +3 -2
- package/dist/{memoryAdapter-BJjyLR-1.d.cts → memoryAdapter-DxEKaG0b.d.ts} +3 -2
- package/dist/placeholders-C4ftwaII.d.ts +5 -0
- package/dist/placeholders-x_3rt65M.d.cts +5 -0
- package/dist/react.cjs +1952 -3257
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +188 -50
- package/dist/react.d.ts +188 -50
- package/dist/react.js +1763 -3013
- package/dist/react.js.map +1 -1
- package/dist/server-handler.cjs +14 -0
- package/dist/server-handler.cjs.map +1 -0
- package/dist/server-handler.d.cts +3 -0
- package/dist/server-handler.d.ts +3 -0
- package/dist/server-handler.js +14 -0
- package/dist/server-handler.js.map +1 -0
- package/dist/server.cjs +1096 -532
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +77 -154
- package/dist/server.d.ts +77 -154
- package/dist/server.js +1050 -448
- package/dist/server.js.map +1 -1
- package/dist/storage.cjs +239 -232
- package/dist/storage.cjs.map +1 -1
- package/dist/storage.d.cts +21 -5
- package/dist/storage.d.ts +21 -5
- package/dist/storage.js +227 -207
- package/dist/storage.js.map +1 -1
- package/dist/styles/animated-image.css +112 -0
- package/dist/styles/dropzone-and-tiles.css +247 -0
- package/dist/styles/modals-and-dock.css +248 -0
- package/dist/styles/primitives.css +197 -0
- package/dist/styles/quality-and-inputs.css +184 -0
- package/dist/styles/tokens.css +123 -0
- package/dist/styles.css +39 -24
- package/dist/types-BrP-o9nd.d.cts +85 -0
- package/dist/types-sZ3KoFQY.d.ts +85 -0
- package/dist/types-yShGNQnW.d.cts +61 -0
- package/dist/types-yShGNQnW.d.ts +61 -0
- package/dist/{types-CiLY0DwB.d.cts → upload-C5Trd6Dh.d.cts} +27 -53
- package/dist/{types-CiLY0DwB.d.ts → upload-C5Trd6Dh.d.ts} +27 -53
- package/package.json +92 -32
- package/dist/blurService-DqsnfPTd.d.cts +0 -172
- 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
|
[](https://upload-kit.buildwithsherbo.com)
|
|
6
6
|
[](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
|
-
- **
|
|
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
|
-
#
|
|
39
|
-
npm install img-upload-kit
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
# bun
|
|
45
|
-
|
|
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 `>=
|
|
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
|
|
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
|
|
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 |
|
|
60
|
+
| Preset | Max Dimensions | Max Size | Quality | Default Formats | Notes |
|
|
75
61
|
| --- | --- | --- | --- | --- | --- |
|
|
76
|
-
| `thumbnail` | `600x600` | 5 MB | Q78 | JPEG, PNG, WebP, AVIF |
|
|
77
|
-
| `avatar` | `512x512` | 5 MB | Q80 | JPEG, PNG, WebP, AVIF |
|
|
78
|
-
| `card` | `1200x900` | 8 MB | Q85 | JPEG, PNG, WebP, AVIF
|
|
79
|
-
| `cover` | `1920x1080` | 10 MB | Q88 | JPEG, PNG, WebP, AVIF
|
|
80
|
-
| `hero` | `2560x1440` | 15 MB | Q92 | JPEG, PNG, WebP, AVIF
|
|
81
|
-
| `logo` | `800x800` | 5 MB | Q90 | JPEG, PNG, WebP, AVIF
|
|
82
|
-
| `original` | `4096x4096` | 25 MB | Q95 | JPEG, PNG, WebP, AVIF
|
|
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
|
-
|
|
89
|
-
folder: '
|
|
90
|
-
maxDimensions: { width:
|
|
91
|
-
|
|
92
|
-
|
|
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
|
-
##
|
|
92
|
+
## Server Uploader & Secure-by-Default HTTP Handler
|
|
101
93
|
|
|
102
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
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
|
|
129
|
-
const
|
|
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
|
-
<
|
|
135
|
-
|
|
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
|
|
192
|
+
## AnimatedImage (Progressive Blur-Up & Settle Reveal)
|
|
210
193
|
|
|
211
|
-
`<AnimatedImage />`
|
|
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.
|
|
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
|
|
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
|
-
##
|
|
221
|
+
## The 4 Styling Tiers
|
|
242
222
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
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
|
-
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
## Documentation & Links
|
|
252
231
|
|
|
253
|
-
|
|
254
|
-
-
|
|
255
|
-
-
|
|
256
|
-
-
|
|
257
|
-
-
|
|
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).
|