@vivekjha659/react-image-editor 0.1.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/LICENSE +21 -0
- package/Readme.md +138 -0
- package/dist/index.d.ts +99 -0
- package/dist/index.js +5293 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +5332 -0
- package/dist/index.mjs.map +1 -0
- package/dist/style.css +1 -0
- package/package.json +53 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Vivek
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/Readme.md
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# @vivekjha659/react-image-editor
|
|
2
|
+
|
|
3
|
+
A portable, WhatsApp-style image editor modal for React and Next.js apps —
|
|
4
|
+
crop, freehand drawing, shapes (rectangle / circle / arrow / double-arrow /
|
|
5
|
+
curve), and text overlays. Ships with a dedicated mobile UI and a separate
|
|
6
|
+
desktop sidebar UI.
|
|
7
|
+
|
|
8
|
+
## Install
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm install @vivekjha659/react-image-editor
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
You'll also need these peer dependencies if your project doesn't already
|
|
15
|
+
have them:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install react react-dom react-konva konva lucide-react react-icons react-toastify
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Import the stylesheet once, anywhere in your app (it's plain compiled CSS —
|
|
22
|
+
no Tailwind setup required on your end, even if you don't use Tailwind):
|
|
23
|
+
|
|
24
|
+
```js
|
|
25
|
+
import "@vivekjha659/react-image-editor/style.css";
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Quick start
|
|
29
|
+
|
|
30
|
+
```jsx
|
|
31
|
+
import { useState } from "react";
|
|
32
|
+
import { ImageEditorModal } from "@vivekjha659/react-image-editor";
|
|
33
|
+
|
|
34
|
+
function Example() {
|
|
35
|
+
const [isOpen, setIsOpen] = useState(true);
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<ImageEditorModal
|
|
39
|
+
isOpen={isOpen}
|
|
40
|
+
onClose={() => setIsOpen(false)}
|
|
41
|
+
image={{ kind: "url", url: "https://example.com/photo.jpg", name: "photo.jpg" }}
|
|
42
|
+
onSave={(result) => {
|
|
43
|
+
// result = { blob, file, dataUrl, width, height }
|
|
44
|
+
console.log(result.file); // a File, ready to upload
|
|
45
|
+
}}
|
|
46
|
+
/>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Image input
|
|
52
|
+
|
|
53
|
+
`image` accepts any of these shapes:
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
type EditableImageSource =
|
|
57
|
+
| { kind: "url"; url: string; name?: string }
|
|
58
|
+
| { kind: "file"; file: File; name?: string }
|
|
59
|
+
| { kind: "blob"; blob: Blob; name?: string }
|
|
60
|
+
| { kind: "dataUrl"; dataUrl: string; name?: string };
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Use whichever matches what you already have — a remote URL, a `File` from
|
|
64
|
+
an `<input type="file">`, a `Blob`, or a base64 data URL. No manual
|
|
65
|
+
conversion needed.
|
|
66
|
+
|
|
67
|
+
### Save result
|
|
68
|
+
|
|
69
|
+
`onSave` receives an `ImageEditResult`:
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
interface ImageEditResult {
|
|
73
|
+
blob: Blob;
|
|
74
|
+
file: File;
|
|
75
|
+
dataUrl: string;
|
|
76
|
+
width: number;
|
|
77
|
+
height: number;
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
All four are derived from the same flattened canvas — pick whichever your
|
|
82
|
+
upload pipeline needs (`file`/`blob` for `FormData` uploads, `dataUrl` for
|
|
83
|
+
inline previews, `width`/`height` for validation or layout).
|
|
84
|
+
|
|
85
|
+
## Next.js usage
|
|
86
|
+
|
|
87
|
+
This component reads `window`/`document` on mount (canvas, Konva, object
|
|
88
|
+
URLs) and **cannot be server-rendered**. In the Next.js App Router, every
|
|
89
|
+
component is a Server Component by default, so you must both:
|
|
90
|
+
|
|
91
|
+
1. Import it with `next/dynamic` and `ssr: false`, and
|
|
92
|
+
2. Only render it from inside a Client Component (a file with its own
|
|
93
|
+
`"use client"` at the top) — `ssr: false` is not itself valid inside a
|
|
94
|
+
Server Component.
|
|
95
|
+
|
|
96
|
+
```jsx
|
|
97
|
+
// components/Editor.jsx
|
|
98
|
+
"use client";
|
|
99
|
+
|
|
100
|
+
import dynamic from "next/dynamic";
|
|
101
|
+
|
|
102
|
+
const ImageEditorModal = dynamic(
|
|
103
|
+
() =>
|
|
104
|
+
import("@vivekjha659/react-image-editor").then((m) => m.ImageEditorModal),
|
|
105
|
+
{ ssr: false },
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
export default function Editor({ isOpen, onClose, image, onSave }) {
|
|
109
|
+
return (
|
|
110
|
+
<ImageEditorModal
|
|
111
|
+
isOpen={isOpen}
|
|
112
|
+
onClose={onClose}
|
|
113
|
+
image={image}
|
|
114
|
+
onSave={onSave}
|
|
115
|
+
/>
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Then import `components/Editor.jsx` from wherever you need it — Server or
|
|
121
|
+
Client Components alike, since dynamic-loading is already handled inside
|
|
122
|
+
that wrapper.
|
|
123
|
+
|
|
124
|
+
## API
|
|
125
|
+
|
|
126
|
+
| Export | Description |
|
|
127
|
+
|---|---|
|
|
128
|
+
| `ImageEditorModal` | The editor modal component. |
|
|
129
|
+
| `resolveImageSource(source)` | Normalizes an `EditableImageSource` into an `<img>`-usable `src`. Used internally; exported for advanced/custom flows. |
|
|
130
|
+
| `getImageSourceKey(source)` | Stable dependency-array key for an `EditableImageSource`. |
|
|
131
|
+
| `buildImageEditResult(canvas, options?)` | Builds an `ImageEditResult` from a canvas. Used internally by the save flow; exported for advanced/custom flows. |
|
|
132
|
+
|
|
133
|
+
Full type definitions ship in `dist/index.d.ts` — TypeScript/Next.js
|
|
134
|
+
consumers get autocomplete and inline docs out of the box.
|
|
135
|
+
|
|
136
|
+
## License
|
|
137
|
+
|
|
138
|
+
MIT © Vivek
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The image to load into the editor. Accepts a remote/static URL, a File
|
|
5
|
+
* (e.g. from an <input type="file">), a Blob, or an already-encoded data
|
|
6
|
+
* URL. Object URLs created internally for "file"/"blob" sources are
|
|
7
|
+
* automatically revoked when the source changes or the modal unmounts.
|
|
8
|
+
*/
|
|
9
|
+
export type EditableImageSource =
|
|
10
|
+
| { kind: "url"; url: string; name?: string }
|
|
11
|
+
| { kind: "file"; file: File; name?: string }
|
|
12
|
+
| { kind: "blob"; blob: Blob; name?: string }
|
|
13
|
+
| { kind: "dataUrl"; dataUrl: string; name?: string };
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* The result handed to `onSave` once the user confirms their edit. All four
|
|
17
|
+
* representations are derived from the same flattened canvas in a single
|
|
18
|
+
* pass — use whichever your upload pipeline needs.
|
|
19
|
+
*/
|
|
20
|
+
export interface ImageEditResult {
|
|
21
|
+
blob: Blob;
|
|
22
|
+
file: File;
|
|
23
|
+
dataUrl: string;
|
|
24
|
+
width: number;
|
|
25
|
+
height: number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface ImageEditorModalProps {
|
|
29
|
+
/** Controls whether the modal is rendered/visible. */
|
|
30
|
+
isOpen: boolean;
|
|
31
|
+
/** Called when the user cancels/dismisses the editor without saving. */
|
|
32
|
+
onClose: () => void;
|
|
33
|
+
/** The image to edit. See {@link EditableImageSource}. */
|
|
34
|
+
image: EditableImageSource;
|
|
35
|
+
/** Called with the edited image once the user confirms save/send. */
|
|
36
|
+
onSave: (result: ImageEditResult) => void;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* A portable, WhatsApp-style image editor modal — crop, freehand drawing,
|
|
41
|
+
* shapes (rectangle / circle / arrow / double-arrow / curve), and text
|
|
42
|
+
* overlays, with a dedicated mobile UI and a desktop sidebar UI.
|
|
43
|
+
*
|
|
44
|
+
* This component reads `window`/`document` on mount and must not be
|
|
45
|
+
* server-rendered. In Next.js, import it with:
|
|
46
|
+
*
|
|
47
|
+
* ```ts
|
|
48
|
+
* const ImageEditorModal = dynamic(
|
|
49
|
+
* () => import("@yourscope/react-image-editor").then((m) => m.ImageEditorModal),
|
|
50
|
+
* { ssr: false },
|
|
51
|
+
* );
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
export declare function ImageEditorModal(
|
|
55
|
+
props: ImageEditorModalProps,
|
|
56
|
+
): React.JSX.Element | null;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Normalizes any {@link EditableImageSource} into a usable <img>/canvas src.
|
|
60
|
+
* Returns a `cleanup()` you must call (e.g. from a `useEffect` return) to
|
|
61
|
+
* revoke any object URL created for "file"/"blob" sources. Called
|
|
62
|
+
* internally by `ImageEditorModal`; exported for advanced/custom usage.
|
|
63
|
+
*/
|
|
64
|
+
export declare function resolveImageSource(
|
|
65
|
+
source: EditableImageSource | null | undefined,
|
|
66
|
+
): {
|
|
67
|
+
src: string | null;
|
|
68
|
+
name: string | undefined;
|
|
69
|
+
isObjectUrl: boolean;
|
|
70
|
+
cleanup: () => void;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Produces a stable string key for an {@link EditableImageSource}, suitable
|
|
75
|
+
* for use as a `useEffect`/`useMemo` dependency array entry — avoids
|
|
76
|
+
* re-resolving (and re-revoking) object URLs just because a caller passed a
|
|
77
|
+
* fresh object literal with the same underlying value.
|
|
78
|
+
*/
|
|
79
|
+
export declare function getImageSourceKey(
|
|
80
|
+
source: EditableImageSource | null | undefined,
|
|
81
|
+
): string | null;
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Builds an {@link ImageEditResult} from a canvas — blob, File, dataUrl, and
|
|
85
|
+
* dimensions, all derived from a single `canvas.toBlob()` call. Called
|
|
86
|
+
* internally by `ImageEditorModal`'s save flow; exported for advanced/
|
|
87
|
+
* custom usage.
|
|
88
|
+
*/
|
|
89
|
+
export declare function buildImageEditResult(
|
|
90
|
+
canvas: HTMLCanvasElement,
|
|
91
|
+
options?: {
|
|
92
|
+
/** Defaults to "edited-image.png" */
|
|
93
|
+
fileName?: string;
|
|
94
|
+
/** Defaults to "image/png" */
|
|
95
|
+
mimeType?: string;
|
|
96
|
+
/** Only used for lossy mime types (e.g. "image/jpeg") */
|
|
97
|
+
quality?: number;
|
|
98
|
+
},
|
|
99
|
+
): Promise<ImageEditResult>;
|