@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 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 };