Skip to content

Commit 3e87680

Browse files
committed
value reset
1 parent b1cbf73 commit 3e87680

File tree

4 files changed

+200
-134
lines changed

4 files changed

+200
-134
lines changed

docs/docs/getting-started.mdx

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,3 +68,30 @@ function MyApp() {
6868
readonly
6969
/>
7070
```
71+
72+
### Reset
73+
74+
```jsx live
75+
function MyApp() {
76+
const imageUploadRef = React.useRef(null);
77+
78+
return (
79+
<div>
80+
<ImageUpload
81+
ref={imageUploadRef}
82+
value={[
83+
"../img/undraw_docusaurus_mountain.svg",
84+
"../img/undraw_docusaurus_react.svg",
85+
"../img/undraw_docusaurus_tree.svg",
86+
]}
87+
/>
88+
<button
89+
className="button button--secondary margin-top--md"
90+
onClick={() => imageUploadRef.current?.reset()}
91+
>
92+
Reset
93+
</button>
94+
</div>
95+
);
96+
}
97+
```

packages/react-image-upload/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# @fourcels/react-image-upload
22

3+
## 0.5.13
4+
5+
### Patch Changes
6+
7+
- value reset
8+
39
## 0.5.12
410

511
### Patch Changes

packages/react-image-upload/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@fourcels/react-image-upload",
3-
"version": "0.5.12",
3+
"version": "0.5.13",
44
"type": "module",
55
"description": "A image upload component for React",
66
"main": "./dist/index.js",
Lines changed: 166 additions & 133 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
import React, { useCallback } from "react";
1+
import React, {
2+
forwardRef,
3+
useCallback,
4+
useImperativeHandle,
5+
useState,
6+
} from "react";
27
import { DropzoneOptions } from "react-dropzone";
38
import { PhotoProvider, PhotoView } from "react-photo-view";
49
import { v7 as uuidv7 } from "uuid";
@@ -75,140 +80,168 @@ export type ImageUploadProps = {
7580
children?: React.ReactNode;
7681
};
7782

78-
export function ImageUpload(props: ImageUploadProps) {
79-
const {
80-
width = 100,
81-
height = width,
82-
value = [],
83-
max = Infinity,
84-
onChange,
85-
onUpload = defaultUpload,
86-
readonly,
87-
dropzoneOptions,
88-
photoProviderProps,
89-
className,
90-
itemClassName,
91-
dropzoneClassName,
92-
children,
93-
} = props;
94-
95-
const [images, setImages] = React.useState<ImageItem[]>(() => {
96-
let valueInner = value;
97-
if (!Array.isArray(valueInner)) {
98-
valueInner = [valueInner];
99-
}
100-
101-
return valueInner
102-
.map<ImageItem>((item) => {
103-
if (typeof item === "string") {
104-
item = { url: item };
83+
export type ImageUploadRef = {
84+
reset: (value?: string | ValueItem | (string | ValueItem)[]) => void;
85+
value: ImageItem[];
86+
};
87+
88+
export const ImageUpload = forwardRef<ImageUploadRef, ImageUploadProps>(
89+
(props, ref) => {
90+
const {
91+
width = 100,
92+
height = width,
93+
value = [],
94+
max = Infinity,
95+
onChange,
96+
onUpload = defaultUpload,
97+
readonly,
98+
dropzoneOptions,
99+
photoProviderProps,
100+
className,
101+
itemClassName,
102+
dropzoneClassName,
103+
children,
104+
} = props;
105+
106+
const getImages = useCallback(
107+
(value: string | ValueItem | (string | ValueItem)[]) => {
108+
if (!Array.isArray(value)) {
109+
value = [value];
105110
}
106-
return {
107-
id: uuidv7(),
108-
...item,
109-
};
110-
})
111-
.slice(0, max);
112-
});
113-
114-
const onDropAccepted = useCallback(
115-
async (acceptedFiles: File[]) => {
116-
const newImages = acceptedFiles
117-
.slice(0, max - images.length)
118-
.map<ImageItem>((file) => ({
119-
id: uuidv7(),
120-
name: file.name,
121-
loading: true,
122-
file,
123-
}));
124-
setImages((images) => {
125-
images = images.concat(newImages);
126-
Promise.all(
127-
newImages.map(async (item) => {
128-
item.url = await onUpload?.(item.file);
129-
item.loading = false;
130-
setImages((images) => {
131-
return [...images];
132-
});
111+
112+
return value
113+
.map<ImageItem>((item) => {
114+
if (typeof item === "string") {
115+
item = { url: item };
116+
}
117+
return {
118+
id: uuidv7(),
119+
...item,
120+
};
133121
})
134-
).then(() => onChange?.(images));
122+
.slice(0, max);
123+
},
124+
[max]
125+
);
126+
127+
const [images, setImages] = useState<ImageItem[]>(() => getImages(value));
128+
129+
useImperativeHandle(
130+
ref,
131+
() => ({
132+
reset: (val = value) => {
133+
setImages(getImages(val));
134+
},
135+
get value() {
136+
return images;
137+
},
138+
}),
139+
[value, images]
140+
);
141+
142+
const onDropAccepted = useCallback(
143+
async (acceptedFiles: File[]) => {
144+
const newImages = acceptedFiles
145+
.slice(0, max - images.length)
146+
.map<ImageItem>((file) => ({
147+
id: uuidv7(),
148+
name: file.name,
149+
loading: true,
150+
file,
151+
}));
152+
setImages((images) => {
153+
images = images.concat(newImages);
154+
Promise.all(
155+
newImages.map(async (item) => {
156+
item.url = await onUpload?.(item.file);
157+
item.loading = false;
158+
setImages((images) => {
159+
return [...images];
160+
});
161+
})
162+
).then(() => onChange?.(images));
163+
return images;
164+
});
165+
},
166+
[images.length, max]
167+
);
168+
169+
const onRemoveImage = useCallback((idx: number) => {
170+
setImages((images) => {
171+
images = images.filter((_, index) => index != idx);
172+
onChange?.(images);
135173
return images;
136174
});
137-
},
138-
[images.length, max]
139-
);
140-
141-
const onRemoveImage = useCallback((idx: number) => {
142-
setImages((images) => {
143-
images = images.filter((_, index) => index != idx);
144-
onChange?.(images);
145-
return images;
146-
});
147-
}, []);
148-
149-
return (
150-
<PhotoProvider {...photoProviderProps}>
151-
<div className={clsx("ImageUpload__root", className)}>
152-
<AnimatePresence mode="popLayout">
153-
{images.map((item, idx) => (
154-
<motion.div
155-
key={item.id}
156-
className={clsx("ImageUpload__item", itemClassName)}
157-
style={{ height, width }}
158-
initial={{ opacity: 0 }}
159-
animate={{ opacity: 1 }}
160-
exit={{ opacity: 0 }}
161-
>
162-
{item.loading ? (
163-
<div className="ImageUpload__loading"></div>
164-
) : (
165-
<>
166-
<img
167-
src={item.url}
168-
className="ImageUpload__img"
169-
alt={item.name}
170-
/>
171-
<PhotoView src={item.url}>
172-
<div className="ImageUpload__preview" title="Preview image">
173-
<PreviewIcon />
174-
</div>
175-
</PhotoView>
176-
{!readonly && (
177-
<span
178-
onClick={() => onRemoveImage(idx)}
179-
className="ImageUpload__remove"
180-
title="Remove image"
181-
>
182-
<RemoveIcon />
183-
</span>
184-
)}
185-
</>
186-
)}
187-
</motion.div>
188-
))}
189-
{!readonly && images.length < max && (
190-
<motion.div
191-
key="dropzone"
192-
initial={{ opacity: 0 }}
193-
animate={{ opacity: 1 }}
194-
exit={{ opacity: 0 }}
195-
>
196-
<Dropzone
197-
options={{
198-
onDropAccepted,
199-
accept: { "image/*": [] },
200-
...dropzoneOptions,
201-
}}
202-
className={dropzoneClassName}
203-
width={width}
204-
height={height}
175+
}, []);
176+
177+
return (
178+
<PhotoProvider {...photoProviderProps}>
179+
<div className={clsx("ImageUpload__root", className)}>
180+
<AnimatePresence mode="popLayout">
181+
{images.map((item, idx) => (
182+
<motion.div
183+
key={item.id}
184+
className={clsx("ImageUpload__item", itemClassName)}
185+
style={{ height, width }}
186+
initial={{ opacity: 0 }}
187+
animate={{ opacity: 1 }}
188+
exit={{ opacity: 0 }}
205189
>
206-
{children}
207-
</Dropzone>
208-
</motion.div>
209-
)}
210-
</AnimatePresence>
211-
</div>
212-
</PhotoProvider>
213-
);
214-
}
190+
{item.loading ? (
191+
<div className="ImageUpload__loading"></div>
192+
) : (
193+
<>
194+
<img
195+
src={item.url}
196+
className="ImageUpload__img"
197+
alt={item.name}
198+
/>
199+
<PhotoView src={item.url}>
200+
<div
201+
className="ImageUpload__preview"
202+
title="Preview image"
203+
>
204+
<PreviewIcon />
205+
</div>
206+
</PhotoView>
207+
{!readonly && (
208+
<span
209+
onClick={() => onRemoveImage(idx)}
210+
className="ImageUpload__remove"
211+
title="Remove image"
212+
>
213+
<RemoveIcon />
214+
</span>
215+
)}
216+
</>
217+
)}
218+
</motion.div>
219+
))}
220+
{!readonly && images.length < max && (
221+
<motion.div
222+
key="dropzone"
223+
animate={{ opacity: 1 }}
224+
exit={{ opacity: 0 }}
225+
>
226+
<Dropzone
227+
options={{
228+
onDropAccepted,
229+
accept: { "image/*": [] },
230+
...dropzoneOptions,
231+
}}
232+
className={dropzoneClassName}
233+
width={width}
234+
height={height}
235+
>
236+
{children}
237+
</Dropzone>
238+
</motion.div>
239+
)}
240+
</AnimatePresence>
241+
</div>
242+
</PhotoProvider>
243+
);
244+
}
245+
);
246+
247+
ImageUpload.displayName = "ImageUpload";

0 commit comments

Comments
 (0)