Skip to content

Commit 835798b

Browse files
committed
feat: add transform
1 parent 8779dc6 commit 835798b

File tree

5 files changed

+98
-24
lines changed

5 files changed

+98
-24
lines changed

docs/docs/api.mdx

Lines changed: 38 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,30 +5,51 @@ sidebar_position: 2
55

66
## ImageUploadProps
77

8-
| Name | Description | Type | Default |
9-
| ------------------ | --------------------------------------- | ---------------------------------------------------------------------------------------------------------- | ----------------------------------------- |
10-
| children | custom upload zone | React.ReactNode | |
11-
| width | image width | `number` | 100 |
12-
| height | image height | `number` | `width` |
13-
| value | default images | `string` \| `ImageItem` \| `(string \| ImageItem)[]` | [] |
14-
| onChange | callback for when the images changes | (value: `ImageItem[]`) => void | |
15-
| onUpload | callback for when the drop event occurs | (file: `File`) => `string` \| `Promise<string>` | (file: File) => URL.createObjectURL(file) |
16-
| max | max image number | `number` | Infinity |
17-
| readonly | for preview | `boolean` | |
18-
| className | root className | `string` | |
19-
| itemClassName | image className | `string` | |
20-
| dropzoneClassName | dropzone className | `string` | |
21-
| dropzoneOptions | | [DropzoneOptions](https://react-dropzone.js.org/#section-components) | |
22-
| photoProviderProps | | Omit\<[PhotoProviderProps](https://react-photo-view.vercel.app/en-US/docs/api#photoprovider), "children"\> | |
23-
24-
### Types
8+
| Name | Description | Type | Default |
9+
| ------------------ | --------------------------------------- | ------------------------------------------------------- | ---------------- |
10+
| children | custom upload zone | React.ReactNode | |
11+
| width | image width | `number` | 100 |
12+
| height | image height | `number` | `width` |
13+
| value | default images | `ValueType` | |
14+
| onChange | callback for when the images changes | (value: `ValueType`) => void | |
15+
| transform | transform images to value | (images: `ImageItem[]`, max: number) => `ValueType` | defaultTransform |
16+
| onUpload | callback for when the drop event occurs | (file: `File`) => `string` \| `Promise<string>` | defaultUpload |
17+
| max | max image number | `number` | Infinity |
18+
| readonly | for preview | `boolean` | |
19+
| className | root className | `string` | |
20+
| itemClassName | image className | `string` | |
21+
| dropzoneClassName | dropzone className | `string` | |
22+
| dropzoneOptions | | [DropzoneOptions][dropdown-url] | |
23+
| photoProviderProps | | Omit\<[PhotoProviderProps][photoview-url], "children"\> | |
24+
25+
[dropdown-url]: https://react-dropzone.js.org/#section-components
26+
[photoview-url]: https://react-photo-view.vercel.app/en-US/docs/api#photoprovider
27+
28+
### Types & Defaults
2529

2630
```ts
31+
type ValueItem = {
32+
url: string;
33+
name?: string;
34+
};
35+
36+
type ValueType = string | ValueItem | (string | ValueItem)[];
37+
2738
type ImageItem = {
2839
id?: string;
2940
url?: string;
3041
name?: string;
3142
file?: File;
3243
loading?: boolean;
3344
};
45+
46+
const defaultUpload = (file: File) => URL.createObjectURL(file);
47+
48+
const defaultTransform = (images: ImageItem[], max: number): ValueType => {
49+
const value = images.map((item) => item.url);
50+
if (max === 1) {
51+
return value.join(",");
52+
}
53+
return value;
54+
};
3455
```

examples/shadcn/components/InputForm.tsx

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,10 @@ const FormSchema = z.object({
2424
username: z.string().min(2, {
2525
message: "Username must be at least 2 characters.",
2626
}),
27-
images: z.any().array().min(1, {
27+
images: z.string().array().min(1, {
2828
message: "Please upload at least one image.",
2929
}),
30+
avatar: z.string().optional(),
3031
});
3132

3233
export function InputForm() {
@@ -87,6 +88,19 @@ export function InputForm() {
8788
</FormItem>
8889
)}
8990
/>
91+
<FormField
92+
control={form.control}
93+
name="avatar"
94+
render={({ field }) => (
95+
<FormItem>
96+
<FormLabel>Avatar</FormLabel>
97+
<FormControl>
98+
<ImageUpload max={1} {...field} />
99+
</FormControl>
100+
<FormMessage />
101+
</FormItem>
102+
)}
103+
/>
90104
<Button type="submit">Submit</Button>
91105
</form>
92106
</Form>

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.6.1
4+
5+
### Patch Changes
6+
7+
- feat: add transform
8+
39
## 0.6.0
410

511
### Minor 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.6.0",
3+
"version": "0.6.1",
44
"type": "module",
55
"description": "A image upload component for React",
66
"main": "./dist/index.js",

packages/react-image-upload/src/ImageUpload.tsx

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import "react-photo-view/dist/react-photo-view.css";
1515
import "./style.css";
1616
import { PhotoProviderProps } from "react-photo-view/dist/PhotoProvider";
1717
import { Dropzone } from "./Dropzone";
18-
import { use } from "motion/react-client";
18+
import { image, use } from "motion/react-client";
1919

2020
const RemoveIcon = () => (
2121
<svg
@@ -52,6 +52,13 @@ const PreviewIcon = () => (
5252
</svg>
5353
);
5454

55+
type ValueItem = {
56+
url: string;
57+
name?: string;
58+
};
59+
60+
type ValueType = string | ValueItem | (string | ValueItem)[];
61+
5562
type ImageItem = {
5663
id?: string;
5764
url?: string;
@@ -60,15 +67,27 @@ type ImageItem = {
6067
loading?: boolean;
6168
};
6269

70+
const checkValue = (a: ValueType, b: ValueType) => {
71+
return a === b || JSON.stringify(a) === JSON.stringify(b);
72+
};
73+
6374
const defaultUpload = (file: File) => URL.createObjectURL(file);
75+
const defaultTransform = (images: ImageItem[], max: number): ValueType => {
76+
const value = images.map((item) => item.url);
77+
if (max === 1) {
78+
return value.join(",");
79+
}
80+
return value;
81+
};
6482

6583
export type ImageUploadProps = {
6684
width?: number;
6785
height?: number;
6886
dropzoneOptions?: DropzoneOptions;
6987
photoProviderProps?: Omit<PhotoProviderProps, "children">;
70-
value?: string | ImageItem | (string | ImageItem)[];
71-
onChange?: (value: ImageItem[]) => void;
88+
value?: ValueType;
89+
onChange?: (value: ValueType) => void;
90+
transform?: (value: ImageItem[], max: number) => ValueType;
7291
max?: number;
7392
onUpload?: (file: File) => string | Promise<string>;
7493
readonly?: boolean;
@@ -87,6 +106,7 @@ export const ImageUpload = forwardRef<HTMLElement, ImageUploadProps>(
87106
max = Infinity,
88107
onChange,
89108
onUpload = defaultUpload,
109+
transform = defaultTransform,
90110
readonly,
91111
dropzoneOptions,
92112
photoProviderProps,
@@ -96,13 +116,17 @@ export const ImageUpload = forwardRef<HTMLElement, ImageUploadProps>(
96116
children,
97117
} = props;
98118

119+
const valueRef = useRef<ValueType>();
99120
const dropzoneRef = useRef<HTMLElement>(null);
100121

101122
useImperativeHandle(ref, () => dropzoneRef.current);
102123

103124
const [images, setImages] = useState<ImageItem[]>([]);
104125

105126
useEffect(() => {
127+
if (checkValue(valueRef.current, value)) {
128+
return;
129+
}
106130
let newImages = [];
107131
if (value) {
108132
let innerValue = value;
@@ -123,9 +147,18 @@ export const ImageUpload = forwardRef<HTMLElement, ImageUploadProps>(
123147
})
124148
.slice(0, max);
125149
}
150+
valueRef.current = value;
126151
setImages(newImages);
127152
}, [value, max]);
128153

154+
const onChangeInner = useCallback(
155+
(images: ImageItem[]) => {
156+
valueRef.current = transform?.(images, max);
157+
onChange?.(valueRef.current);
158+
},
159+
[transform, onChange, max]
160+
);
161+
129162
const onDropAccepted = useCallback(
130163
async (acceptedFiles: File[]) => {
131164
const addImages = acceptedFiles
@@ -146,15 +179,15 @@ export const ImageUpload = forwardRef<HTMLElement, ImageUploadProps>(
146179
return [...images];
147180
});
148181
})
149-
).then(() => onChange?.(newImages));
182+
).then(() => onChangeInner(newImages));
150183
},
151184
[images, max, onChange]
152185
);
153186

154187
const onRemoveImage = useCallback(
155188
(idx: number) => {
156189
const newImages = images.filter((_, index) => index != idx);
157-
onChange?.(newImages);
190+
onChangeInner(newImages);
158191
setImages(newImages);
159192
},
160193
[images, onChange]

0 commit comments

Comments
 (0)