@xsolla/xui-image-uploader 0.147.1
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/README.md +258 -0
- package/native/index.d.mts +65 -0
- package/native/index.d.ts +65 -0
- package/native/index.js +668 -0
- package/native/index.js.map +1 -0
- package/native/index.mjs +641 -0
- package/native/index.mjs.map +1 -0
- package/package.json +58 -0
- package/web/index.d.mts +65 -0
- package/web/index.d.ts +65 -0
- package/web/index.js +711 -0
- package/web/index.js.map +1 -0
- package/web/index.mjs +677 -0
- package/web/index.mjs.map +1 -0
package/README.md
ADDED
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
# ImageUploader
|
|
2
|
+
|
|
3
|
+
A cross-platform React image uploader component supporting click-to-pick, drag-and-drop (web), controlled and uncontrolled usage, image preview with hover-to-remove, loading and error states, and a wide horizontal layout.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @xsolla/xui-image-uploader
|
|
9
|
+
# or
|
|
10
|
+
yarn add @xsolla/xui-image-uploader
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Demo
|
|
14
|
+
|
|
15
|
+
### Basic (Uncontrolled)
|
|
16
|
+
|
|
17
|
+
```tsx
|
|
18
|
+
import * as React from 'react';
|
|
19
|
+
import { ImageUploader, type ImageUploaderFile } from '@xsolla/xui-image-uploader';
|
|
20
|
+
|
|
21
|
+
export default function Basic() {
|
|
22
|
+
const handleUpload = (file: ImageUploaderFile) => {
|
|
23
|
+
console.log('Picked file:', file.name, file.size);
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
return <ImageUploader onUpload={handleUpload} />;
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Controlled
|
|
31
|
+
|
|
32
|
+
```tsx
|
|
33
|
+
import * as React from 'react';
|
|
34
|
+
import {
|
|
35
|
+
ImageUploader,
|
|
36
|
+
type ImageUploaderValue,
|
|
37
|
+
} from '@xsolla/xui-image-uploader';
|
|
38
|
+
|
|
39
|
+
export default function Controlled() {
|
|
40
|
+
const [value, setValue] = React.useState<ImageUploaderValue | null>(null);
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<ImageUploader
|
|
44
|
+
value={value}
|
|
45
|
+
onChange={setValue}
|
|
46
|
+
placeholder="Upload avatar"
|
|
47
|
+
/>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Wide View with Description
|
|
53
|
+
|
|
54
|
+
```tsx
|
|
55
|
+
import * as React from 'react';
|
|
56
|
+
import { ImageUploader } from '@xsolla/xui-image-uploader';
|
|
57
|
+
|
|
58
|
+
export default function WideView() {
|
|
59
|
+
return (
|
|
60
|
+
<ImageUploader
|
|
61
|
+
wideView
|
|
62
|
+
placeholder="Upload cover image"
|
|
63
|
+
description="PNG or JPG, up to 5 MB"
|
|
64
|
+
/>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Loading State
|
|
70
|
+
|
|
71
|
+
```tsx
|
|
72
|
+
import * as React from 'react';
|
|
73
|
+
import {
|
|
74
|
+
ImageUploader,
|
|
75
|
+
type ImageUploaderFile,
|
|
76
|
+
} from '@xsolla/xui-image-uploader';
|
|
77
|
+
|
|
78
|
+
export default function WithLoading() {
|
|
79
|
+
const [loading, setLoading] = React.useState(false);
|
|
80
|
+
|
|
81
|
+
const handleUpload = async (file: ImageUploaderFile) => {
|
|
82
|
+
setLoading(true);
|
|
83
|
+
try {
|
|
84
|
+
await uploadToServer(file.file);
|
|
85
|
+
} finally {
|
|
86
|
+
setLoading(false);
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
<ImageUploader
|
|
92
|
+
loading={loading}
|
|
93
|
+
uploadingPlaceholder="Uploading…"
|
|
94
|
+
onUpload={handleUpload}
|
|
95
|
+
/>
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Error State
|
|
101
|
+
|
|
102
|
+
```tsx
|
|
103
|
+
import * as React from 'react';
|
|
104
|
+
import { ImageUploader } from '@xsolla/xui-image-uploader';
|
|
105
|
+
|
|
106
|
+
export default function WithError() {
|
|
107
|
+
return (
|
|
108
|
+
<ImageUploader
|
|
109
|
+
placeholder="cover.png"
|
|
110
|
+
errorMessage="File is too large (max 5 MB)."
|
|
111
|
+
/>
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### React Native (Custom Picker)
|
|
117
|
+
|
|
118
|
+
On native there is no DOM `<input type="file">`. Pass an `openPicker` prop that
|
|
119
|
+
returns a normalized `ImageUploaderFile` (e.g. wrapping `expo-image-picker` or
|
|
120
|
+
`react-native-image-picker`).
|
|
121
|
+
|
|
122
|
+
```tsx
|
|
123
|
+
import * as React from 'react';
|
|
124
|
+
import * as ImagePicker from 'expo-image-picker';
|
|
125
|
+
import {
|
|
126
|
+
ImageUploader,
|
|
127
|
+
type ImageUploaderFile,
|
|
128
|
+
} from '@xsolla/xui-image-uploader';
|
|
129
|
+
|
|
130
|
+
export default function Native() {
|
|
131
|
+
const openPicker = async (): Promise<ImageUploaderFile | null> => {
|
|
132
|
+
const result = await ImagePicker.launchImageLibraryAsync({
|
|
133
|
+
mediaTypes: ImagePicker.MediaTypeOptions.Images,
|
|
134
|
+
});
|
|
135
|
+
if (result.canceled) return null;
|
|
136
|
+
const asset = result.assets[0];
|
|
137
|
+
return {
|
|
138
|
+
name: asset.fileName ?? 'image',
|
|
139
|
+
size: asset.fileSize ?? 0,
|
|
140
|
+
uri: asset.uri,
|
|
141
|
+
mimeType: asset.mimeType,
|
|
142
|
+
};
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
return <ImageUploader openPicker={openPicker} />;
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## Anatomy
|
|
150
|
+
|
|
151
|
+
```
|
|
152
|
+
+--------------------------+
|
|
153
|
+
| |
|
|
154
|
+
| [Image] | <- icon (or Spinner while loading)
|
|
155
|
+
| Upload | <- placeholder
|
|
156
|
+
| PNG/JPG, up to 5MB | <- description (wideView only)
|
|
157
|
+
| |
|
|
158
|
+
+--------------------------+
|
|
159
|
+
File is too large… <- errorMessage (when present)
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
When a file has been uploaded, the box renders the image preview and reveals a
|
|
163
|
+
trash-can affordance over a dimming scrim on hover; clicking (or tapping)
|
|
164
|
+
removes the image. On touch and React Native devices — where there is no hover
|
|
165
|
+
— the trash overlay is shown unconditionally so the delete affordance is
|
|
166
|
+
always discoverable.
|
|
167
|
+
|
|
168
|
+
## API Reference
|
|
169
|
+
|
|
170
|
+
### ImageUploader
|
|
171
|
+
|
|
172
|
+
| Prop | Type | Default | Description |
|
|
173
|
+
| :--- | :--- | :------ | :---------- |
|
|
174
|
+
| size | `"xl" \| "lg" \| "md" \| "sm" \| "xs"` | `"xl"` | Visual size of the uploader. |
|
|
175
|
+
| placeholder | `string` | `"Upload"` | Label shown under the icon (or filename in error state). |
|
|
176
|
+
| uploadingPlaceholder | `string` | `"Uploading"` | Label shown while `loading` is true. |
|
|
177
|
+
| description | `React.ReactNode` | - | Helper text below the placeholder. Only rendered when `wideView` is true. |
|
|
178
|
+
| errorMessage | `string` | - | Error message — when provided, the component renders in the error state. |
|
|
179
|
+
| wideView | `boolean` | `false` | Use the horizontal layout (`wideWidth` from sizing tokens). |
|
|
180
|
+
| disabled | `boolean` | `false` | Disabled state. |
|
|
181
|
+
| loading | `boolean` | `false` | Controlled loading state (shows spinner + uploading label). |
|
|
182
|
+
| value | `ImageUploaderValue \| null` | - | Controlled value. When provided, the component reflects this value. |
|
|
183
|
+
| onUpload | `(file: ImageUploaderFile) => void` | - | Fires when the user picks (or drops) a file. Use this to perform the actual upload. |
|
|
184
|
+
| onChange | `(value: ImageUploaderValue \| null) => void` | - | Fires when the displayed value changes — on file pick (with a local data-URL preview) and on remove (`null`). |
|
|
185
|
+
| onDelete | `() => void` | - | Fires when the user removes the image (trash click / clear). |
|
|
186
|
+
| accept | `string` | `"image/*"` | Accepted MIME types for the file input (web only). |
|
|
187
|
+
| openPicker | `() => Promise<ImageUploaderFile \| null>` | - | Native file picker hook. Required on native (no DOM `<input type="file">`). On web, omit this and the component falls back to a hidden file input. |
|
|
188
|
+
| themeMode | `ThemeMode` | - | Override the theme mode for this instance. |
|
|
189
|
+
| themeProductContext | `ThemeProductContext` | - | Override the product theme context for this instance. |
|
|
190
|
+
|
|
191
|
+
### ImageUploaderValue
|
|
192
|
+
|
|
193
|
+
Controlled value shape.
|
|
194
|
+
|
|
195
|
+
```ts
|
|
196
|
+
interface ImageUploaderValue {
|
|
197
|
+
filename: string;
|
|
198
|
+
url?: string;
|
|
199
|
+
}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
> Loading and error states are driven by the `loading` and `errorMessage`
|
|
203
|
+
> props, not by fields on `ImageUploaderValue`.
|
|
204
|
+
|
|
205
|
+
### ImageUploaderFile
|
|
206
|
+
|
|
207
|
+
Normalized file shape produced by the platform picker / drag-drop pipeline.
|
|
208
|
+
On web, `uri` is a data-URL and the original DOM `File` is passed through for
|
|
209
|
+
consumers that need it (e.g. to upload via `FormData` / `fetch`). On native,
|
|
210
|
+
`uri` is a file URI and `file` is omitted.
|
|
211
|
+
|
|
212
|
+
```ts
|
|
213
|
+
interface ImageUploaderFile {
|
|
214
|
+
name: string;
|
|
215
|
+
size: number;
|
|
216
|
+
uri: string;
|
|
217
|
+
mimeType?: string;
|
|
218
|
+
/** The original DOM File (web only) */
|
|
219
|
+
file?: File;
|
|
220
|
+
}
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
## Platform Support
|
|
224
|
+
|
|
225
|
+
This package works on both **web** and **React Native**. The component uses
|
|
226
|
+
cross-platform primitives (`Box`, `Text`, `Spinner`) and gates web-only APIs
|
|
227
|
+
(DOM file input, drag-and-drop, `FileReader`) behind an `isWeb` check. On
|
|
228
|
+
native, supply an `openPicker` prop to integrate with your image picker
|
|
229
|
+
library of choice.
|
|
230
|
+
|
|
231
|
+
### Touch / Mobile
|
|
232
|
+
|
|
233
|
+
On touch web (detected via `matchMedia("(hover: none)")`) and on React Native,
|
|
234
|
+
the component skips the hover-only delete affordance and always shows the
|
|
235
|
+
trash overlay over the image preview, ensuring the remove action is reachable
|
|
236
|
+
without a pointer.
|
|
237
|
+
|
|
238
|
+
### Layout
|
|
239
|
+
|
|
240
|
+
In `wideView`, the component stretches to fill its parent (`width: 100%`) up
|
|
241
|
+
to the design max from the size token (`wideWidth`), so it scales gracefully
|
|
242
|
+
on narrow viewports while capping on desktop. The square (non-wide) variants
|
|
243
|
+
use fixed pixel dimensions from the size tokens.
|
|
244
|
+
|
|
245
|
+
## Accessibility
|
|
246
|
+
|
|
247
|
+
- The picker surface is rendered as a `<button>` on web with an accessible
|
|
248
|
+
label that reflects the current state (`"Upload"`, `"Uploading"`, or
|
|
249
|
+
`"Remove image: <filename>"`).
|
|
250
|
+
- `aria-busy` is set while `loading` is true.
|
|
251
|
+
- `aria-invalid` is set in the error state and the error message is linked
|
|
252
|
+
via `aria-describedby` (and rendered with `role="alert"`).
|
|
253
|
+
- The `description` element (when present) is linked via `aria-describedby`.
|
|
254
|
+
- Decorative icons and the dimming scrim are marked `aria-hidden`.
|
|
255
|
+
- Focus styling only appears for keyboard focus (`:focus-visible`), not for
|
|
256
|
+
pointer focus left over after the native picker is dismissed.
|
|
257
|
+
- The hidden file input is reset after every selection so the same file can
|
|
258
|
+
be re-selected to retry a failed upload.
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { ThemeOverrideProps } from '@xsolla/xui-core';
|
|
3
|
+
|
|
4
|
+
type ImageUploaderSize = "xl" | "lg" | "md" | "sm" | "xs";
|
|
5
|
+
/** Controlled value shape. */
|
|
6
|
+
interface ImageUploaderValue {
|
|
7
|
+
filename: string;
|
|
8
|
+
url?: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Normalized file shape produced by the platform picker / drag-drop pipeline.
|
|
12
|
+
* - `uri` is a data-URL on web and a file URI on native.
|
|
13
|
+
* - On web, the original `File` is passed through for consumers that need it
|
|
14
|
+
* (e.g. to upload via FormData / fetch).
|
|
15
|
+
*/
|
|
16
|
+
type ImageUploaderFile = {
|
|
17
|
+
name: string;
|
|
18
|
+
size: number;
|
|
19
|
+
uri: string;
|
|
20
|
+
mimeType?: string;
|
|
21
|
+
/** The original DOM File (web only) */
|
|
22
|
+
file?: File;
|
|
23
|
+
};
|
|
24
|
+
interface ImageUploaderProps extends ThemeOverrideProps {
|
|
25
|
+
/** Size of the uploader. Figma default is `xl`. */
|
|
26
|
+
size?: ImageUploaderSize;
|
|
27
|
+
/** Placeholder text shown under the icon (e.g. "Upload" or filename in error). */
|
|
28
|
+
placeholder?: string;
|
|
29
|
+
/** Placeholder text shown while the file is uploading. */
|
|
30
|
+
uploadingPlaceholder?: string;
|
|
31
|
+
/** Description below the placeholder. Only rendered when `wideView` is true. Accepts a string or a custom React node. */
|
|
32
|
+
description?: React.ReactNode;
|
|
33
|
+
/** Error message — when provided, component renders in the error state. */
|
|
34
|
+
errorMessage?: string;
|
|
35
|
+
/** Wide view (horizontal layout). When true, box uses `wideWidth` from sizing. */
|
|
36
|
+
wideView?: boolean;
|
|
37
|
+
/** Disabled state. */
|
|
38
|
+
disabled?: boolean;
|
|
39
|
+
/** Controlled loading state (shows spinner + "Uploading" label). */
|
|
40
|
+
loading?: boolean;
|
|
41
|
+
/** Controlled value. When provided, the component reflects this value. */
|
|
42
|
+
value?: ImageUploaderValue | null;
|
|
43
|
+
/**
|
|
44
|
+
* Fires when the user picks (or drops) a file. Use this to perform the
|
|
45
|
+
* actual upload — the component itself does no I/O.
|
|
46
|
+
*/
|
|
47
|
+
onUpload?: (file: ImageUploaderFile) => void;
|
|
48
|
+
/**
|
|
49
|
+
* Fires when the displayed value changes — on file pick (with a local
|
|
50
|
+
* data-URL preview) and on remove (`null`).
|
|
51
|
+
*/
|
|
52
|
+
onChange?: (value: ImageUploaderValue | null) => void;
|
|
53
|
+
/** Fires when the user removes the image (trash click / clear). */
|
|
54
|
+
onDelete?: () => void;
|
|
55
|
+
/** Accepted file types (web only — ignored on native). */
|
|
56
|
+
accept?: string;
|
|
57
|
+
/**
|
|
58
|
+
* Native file picker hook. Required on native (no DOM `<input type="file">`).
|
|
59
|
+
* On web, omit this and the component falls back to a hidden file input.
|
|
60
|
+
*/
|
|
61
|
+
openPicker?: () => Promise<ImageUploaderFile | null>;
|
|
62
|
+
}
|
|
63
|
+
declare const ImageUploader: React.ForwardRefExoticComponent<ImageUploaderProps & React.RefAttributes<HTMLInputElement>>;
|
|
64
|
+
|
|
65
|
+
export { ImageUploader, type ImageUploaderFile, type ImageUploaderProps, type ImageUploaderSize, type ImageUploaderValue };
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { ThemeOverrideProps } from '@xsolla/xui-core';
|
|
3
|
+
|
|
4
|
+
type ImageUploaderSize = "xl" | "lg" | "md" | "sm" | "xs";
|
|
5
|
+
/** Controlled value shape. */
|
|
6
|
+
interface ImageUploaderValue {
|
|
7
|
+
filename: string;
|
|
8
|
+
url?: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Normalized file shape produced by the platform picker / drag-drop pipeline.
|
|
12
|
+
* - `uri` is a data-URL on web and a file URI on native.
|
|
13
|
+
* - On web, the original `File` is passed through for consumers that need it
|
|
14
|
+
* (e.g. to upload via FormData / fetch).
|
|
15
|
+
*/
|
|
16
|
+
type ImageUploaderFile = {
|
|
17
|
+
name: string;
|
|
18
|
+
size: number;
|
|
19
|
+
uri: string;
|
|
20
|
+
mimeType?: string;
|
|
21
|
+
/** The original DOM File (web only) */
|
|
22
|
+
file?: File;
|
|
23
|
+
};
|
|
24
|
+
interface ImageUploaderProps extends ThemeOverrideProps {
|
|
25
|
+
/** Size of the uploader. Figma default is `xl`. */
|
|
26
|
+
size?: ImageUploaderSize;
|
|
27
|
+
/** Placeholder text shown under the icon (e.g. "Upload" or filename in error). */
|
|
28
|
+
placeholder?: string;
|
|
29
|
+
/** Placeholder text shown while the file is uploading. */
|
|
30
|
+
uploadingPlaceholder?: string;
|
|
31
|
+
/** Description below the placeholder. Only rendered when `wideView` is true. Accepts a string or a custom React node. */
|
|
32
|
+
description?: React.ReactNode;
|
|
33
|
+
/** Error message — when provided, component renders in the error state. */
|
|
34
|
+
errorMessage?: string;
|
|
35
|
+
/** Wide view (horizontal layout). When true, box uses `wideWidth` from sizing. */
|
|
36
|
+
wideView?: boolean;
|
|
37
|
+
/** Disabled state. */
|
|
38
|
+
disabled?: boolean;
|
|
39
|
+
/** Controlled loading state (shows spinner + "Uploading" label). */
|
|
40
|
+
loading?: boolean;
|
|
41
|
+
/** Controlled value. When provided, the component reflects this value. */
|
|
42
|
+
value?: ImageUploaderValue | null;
|
|
43
|
+
/**
|
|
44
|
+
* Fires when the user picks (or drops) a file. Use this to perform the
|
|
45
|
+
* actual upload — the component itself does no I/O.
|
|
46
|
+
*/
|
|
47
|
+
onUpload?: (file: ImageUploaderFile) => void;
|
|
48
|
+
/**
|
|
49
|
+
* Fires when the displayed value changes — on file pick (with a local
|
|
50
|
+
* data-URL preview) and on remove (`null`).
|
|
51
|
+
*/
|
|
52
|
+
onChange?: (value: ImageUploaderValue | null) => void;
|
|
53
|
+
/** Fires when the user removes the image (trash click / clear). */
|
|
54
|
+
onDelete?: () => void;
|
|
55
|
+
/** Accepted file types (web only — ignored on native). */
|
|
56
|
+
accept?: string;
|
|
57
|
+
/**
|
|
58
|
+
* Native file picker hook. Required on native (no DOM `<input type="file">`).
|
|
59
|
+
* On web, omit this and the component falls back to a hidden file input.
|
|
60
|
+
*/
|
|
61
|
+
openPicker?: () => Promise<ImageUploaderFile | null>;
|
|
62
|
+
}
|
|
63
|
+
declare const ImageUploader: React.ForwardRefExoticComponent<ImageUploaderProps & React.RefAttributes<HTMLInputElement>>;
|
|
64
|
+
|
|
65
|
+
export { ImageUploader, type ImageUploaderFile, type ImageUploaderProps, type ImageUploaderSize, type ImageUploaderValue };
|