@adamosuiteservices/ui 2.14.2 → 2.15.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/dist/combobox-DGuQtXjP.js +608 -0
- package/dist/combobox-hTCtPMUL.cjs +40 -0
- package/dist/combobox.cjs +1 -1
- package/dist/combobox.js +1 -1
- package/dist/components/ui/combobox/combobox.d.ts +5 -1
- package/dist/components/ui/date-picker-selector/date-picker-selector.d.ts +5 -1
- package/dist/components/ui/file-upload/file-upload.d.ts +4 -1
- package/dist/components/ui/slider/slider.d.ts +1 -1
- package/dist/components/ui/switch/switch.d.ts +1 -1
- package/dist/date-picker-selector.cjs +1 -1
- package/dist/date-picker-selector.js +38 -28
- package/dist/field.cjs +2 -2
- package/dist/field.js +2 -2
- package/dist/file-upload.cjs +5 -3
- package/dist/file-upload.js +191 -150
- package/dist/slider.cjs +5 -5
- package/dist/slider.js +196 -177
- package/dist/styles.css +1 -1
- package/dist/switch.cjs +3 -3
- package/dist/switch.js +105 -85
- package/docs/components/ui/accordion-rounded.md +2 -6
- package/docs/components/ui/avatar.md +3 -1
- package/docs/components/ui/button.md +22 -16
- package/docs/components/ui/card.md +49 -31
- package/docs/components/ui/checkbox.md +44 -8
- package/docs/components/ui/combobox.md +62 -4
- package/docs/components/ui/date-picker-selector.md +90 -26
- package/docs/components/ui/file-upload.md +364 -95
- package/docs/components/ui/icon.md +1 -1
- package/docs/components/ui/input.md +4 -1
- package/docs/components/ui/radio-group.md +1 -1
- package/docs/components/ui/select.md +51 -34
- package/docs/components/ui/sheet.md +3 -9
- package/docs/components/ui/slider.md +120 -99
- package/docs/components/ui/switch.md +408 -408
- package/docs/components/ui/textarea.md +37 -22
- package/docs/components/ui/tooltip.md +5 -2
- package/docs/components/ui/typography.md +63 -39
- package/package.json +1 -1
- package/dist/combobox-CCJGIMFQ.cjs +0 -40
- package/dist/combobox-Cj-GEp-T.js +0 -604
|
@@ -17,6 +17,9 @@ Componente de carga de archivos con funcionalidad **drag & drop**, validación d
|
|
|
17
17
|
- ✅ Límite de cantidad de archivos en modo múltiple
|
|
18
18
|
- ✅ Etiquetas personalizables (i18n)
|
|
19
19
|
- ✅ Estado de arrastre visual (borde y fondo cambian)
|
|
20
|
+
- ✅ Estados de validación con `aria-invalid`
|
|
21
|
+
- ✅ Estados deshabilitados con `disabled`
|
|
22
|
+
- ✅ Detección automática de fieldset disabled
|
|
20
23
|
- ✅ Integración con formularios React
|
|
21
24
|
- ✅ TypeScript completo con tipos exportados
|
|
22
25
|
|
|
@@ -24,7 +27,10 @@ Componente de carga de archivos con funcionalidad **drag & drop**, validación d
|
|
|
24
27
|
|
|
25
28
|
```typescript
|
|
26
29
|
import { FileUpload } from "@adamosuiteservices/ui/file-upload";
|
|
27
|
-
import type {
|
|
30
|
+
import type {
|
|
31
|
+
FileUploadProps,
|
|
32
|
+
FileUploadLabels,
|
|
33
|
+
} from "@adamosuiteservices/ui/file-upload";
|
|
28
34
|
```
|
|
29
35
|
|
|
30
36
|
## Uso Básico
|
|
@@ -38,12 +44,7 @@ import { FileUpload } from "@adamosuiteservices/ui/file-upload";
|
|
|
38
44
|
function MyForm() {
|
|
39
45
|
const [file, setFile] = useState<File | null>(null);
|
|
40
46
|
|
|
41
|
-
return
|
|
42
|
-
<FileUpload
|
|
43
|
-
selectedFile={file}
|
|
44
|
-
onFileSelect={setFile}
|
|
45
|
-
/>
|
|
46
|
-
);
|
|
47
|
+
return <FileUpload selectedFile={file} onFileSelect={setFile} />;
|
|
47
48
|
}
|
|
48
49
|
```
|
|
49
50
|
|
|
@@ -56,17 +57,12 @@ import { FileUpload } from "@adamosuiteservices/ui/file-upload";
|
|
|
56
57
|
function MyForm() {
|
|
57
58
|
const [files, setFiles] = useState<File[]>([]);
|
|
58
59
|
|
|
59
|
-
return
|
|
60
|
-
<FileUpload
|
|
61
|
-
selectedFiles={files}
|
|
62
|
-
onFilesSelect={setFiles}
|
|
63
|
-
multiple
|
|
64
|
-
/>
|
|
65
|
-
);
|
|
60
|
+
return <FileUpload selectedFiles={files} onFilesSelect={setFiles} multiple />;
|
|
66
61
|
}
|
|
67
62
|
```
|
|
68
63
|
|
|
69
64
|
**Configuración por defecto**:
|
|
65
|
+
|
|
70
66
|
- Extensiones aceptadas: `.xls`, `.xlsx`, `.numbers`
|
|
71
67
|
- Tamaño máximo: 50 MB
|
|
72
68
|
- Máximo de archivos (modo múltiple): 10
|
|
@@ -101,7 +97,8 @@ acceptedExtensions?: string[]
|
|
|
101
97
|
Array de extensiones de archivo permitidas. Por defecto: `[".xls", ".xlsx", ".numbers"]`
|
|
102
98
|
|
|
103
99
|
**Ejemplo**:
|
|
104
|
-
|
|
100
|
+
|
|
101
|
+
````tsx
|
|
105
102
|
<FileUpload
|
|
106
103
|
selectedFile={file}
|
|
107
104
|
onFileSelect={setFile}
|
|
@@ -111,17 +108,14 @@ Array de extensiones de archivo permitidas. Por defecto: `[".xls", ".xlsx", ".nu
|
|
|
111
108
|
|
|
112
109
|
```tsx
|
|
113
110
|
maxSizeInMB?: number
|
|
114
|
-
|
|
111
|
+
````
|
|
115
112
|
|
|
116
113
|
Tamaño máximo del archivo en megabytes. Por defecto: `50`
|
|
117
114
|
|
|
118
115
|
**Ejemplo**:
|
|
116
|
+
|
|
119
117
|
```tsx
|
|
120
|
-
<FileUpload
|
|
121
|
-
selectedFile={file}
|
|
122
|
-
onFileSelect={setFile}
|
|
123
|
-
maxSizeInMB={10}
|
|
124
|
-
/>
|
|
118
|
+
<FileUpload selectedFile={file} onFileSelect={setFile} maxSizeInMB={10} />
|
|
125
119
|
```
|
|
126
120
|
|
|
127
121
|
#### labels
|
|
@@ -140,6 +134,7 @@ type FileUploadLabels = {
|
|
|
140
134
|
Etiquetas personalizables para internacionalización o textos específicos.
|
|
141
135
|
|
|
142
136
|
**Ejemplo**:
|
|
137
|
+
|
|
143
138
|
```tsx
|
|
144
139
|
<FileUpload
|
|
145
140
|
selectedFile={file}
|
|
@@ -147,20 +142,39 @@ Etiquetas personalizables para internacionalización o textos específicos.
|
|
|
147
142
|
labels={{
|
|
148
143
|
dragDrop: "Arrastra y suelta tu archivo aquí o",
|
|
149
144
|
selectFile: "Selecciona el archivo",
|
|
150
|
-
fileRequirements: "Archivos permitidos: .xls, .xlsx. Tamaño máximo 50 MB."
|
|
145
|
+
fileRequirements: "Archivos permitidos: .xls, .xlsx. Tamaño máximo 50 MB.",
|
|
151
146
|
}}
|
|
152
147
|
/>
|
|
153
148
|
```
|
|
154
149
|
|
|
150
|
+
#### aria-invalid
|
|
151
|
+
|
|
152
|
+
```tsx
|
|
153
|
+
"aria-invalid"?: boolean
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Marca el componente como inválido, aplicando estilos destructivos. Útil para validación de formularios. Por defecto: `false`
|
|
157
|
+
|
|
158
|
+
**Ejemplo**:
|
|
159
|
+
|
|
160
|
+
```tsx
|
|
161
|
+
<FileUpload
|
|
162
|
+
selectedFile={file}
|
|
163
|
+
onFileSelect={setFile}
|
|
164
|
+
aria-invalid={!!errorMessage}
|
|
165
|
+
/>
|
|
166
|
+
```
|
|
167
|
+
|
|
155
168
|
#### invalid
|
|
156
169
|
|
|
157
170
|
```tsx
|
|
158
171
|
invalid?: boolean
|
|
159
172
|
```
|
|
160
173
|
|
|
161
|
-
|
|
174
|
+
Alternativa a `aria-invalid` para marcar el componente como inválido. Por defecto: `false`
|
|
162
175
|
|
|
163
176
|
**Ejemplo**:
|
|
177
|
+
|
|
164
178
|
```tsx
|
|
165
179
|
<FileUpload
|
|
166
180
|
selectedFile={file}
|
|
@@ -169,6 +183,66 @@ Marca el componente como inválido, aplicando estilos destructivos. Por defecto:
|
|
|
169
183
|
/>
|
|
170
184
|
```
|
|
171
185
|
|
|
186
|
+
#### disabled
|
|
187
|
+
|
|
188
|
+
```tsx
|
|
189
|
+
disabled?: boolean
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Deshabilita el componente, impidiendo toda interacción del usuario. Por defecto: `false`
|
|
193
|
+
|
|
194
|
+
**Comportamiento**:
|
|
195
|
+
|
|
196
|
+
- El área de drag & drop se muestra deshabilitada con opacidad reducida
|
|
197
|
+
- Los archivos seleccionados también se muestran deshabilitados
|
|
198
|
+
- Los botones de eliminar archivos individuales están deshabilitados
|
|
199
|
+
- El botón "Clear all" (en modo múltiple) está deshabilitado
|
|
200
|
+
- Los estados de error visual no se muestran cuando está deshabilitado
|
|
201
|
+
|
|
202
|
+
**Ejemplo**:
|
|
203
|
+
|
|
204
|
+
```tsx
|
|
205
|
+
<FileUpload selectedFile={file} onFileSelect={setFile} disabled />
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
**Nota**: El componente también detecta automáticamente si está dentro de un `<fieldset disabled>` y se deshabilitará automáticamente.
|
|
209
|
+
|
|
210
|
+
#### input
|
|
211
|
+
|
|
212
|
+
```tsx
|
|
213
|
+
input?: ComponentProps<"input">
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Props personalizadas para el elemento `<input type="file">` interno. Útil para personalizar el ID, name, data attributes, y otros atributos HTML.
|
|
217
|
+
|
|
218
|
+
**Comportamiento**:
|
|
219
|
+
- Si no se proporciona un `id`, se genera automáticamente con formato `file-upload-{random}`
|
|
220
|
+
- Las props críticas (`type`, `accept`, `multiple`, `onChange`, `disabled`, `className`) siempre se mantienen para garantizar el funcionamiento correcto
|
|
221
|
+
- Puedes sobrescribir cualquier otra prop HTML del input
|
|
222
|
+
|
|
223
|
+
**Ejemplo**:
|
|
224
|
+
|
|
225
|
+
```tsx
|
|
226
|
+
// ID personalizado para asociar con label
|
|
227
|
+
<FileUpload
|
|
228
|
+
selectedFile={file}
|
|
229
|
+
onFileSelect={setFile}
|
|
230
|
+
input={{ id: "document-upload" }}
|
|
231
|
+
/>
|
|
232
|
+
|
|
233
|
+
// Con múltiples atributos para integración con formularios
|
|
234
|
+
<FileUpload
|
|
235
|
+
selectedFile={file}
|
|
236
|
+
onFileSelect={setFile}
|
|
237
|
+
input={{
|
|
238
|
+
id: "invoice-file",
|
|
239
|
+
name: "invoice",
|
|
240
|
+
"data-testid": "invoice-input",
|
|
241
|
+
"aria-describedby": "invoice-help",
|
|
242
|
+
}}
|
|
243
|
+
/>
|
|
244
|
+
```
|
|
245
|
+
|
|
172
246
|
#### onInvalidFile
|
|
173
247
|
|
|
174
248
|
```tsx
|
|
@@ -178,6 +252,7 @@ onInvalidFile?: (file: File, reason: "extension" | "size") => void
|
|
|
178
252
|
Callback invocado cuando un archivo no pasa la validación. Recibe el archivo y el motivo del rechazo.
|
|
179
253
|
|
|
180
254
|
**Ejemplo**:
|
|
255
|
+
|
|
181
256
|
```tsx
|
|
182
257
|
const handleInvalidFile = (file: File, reason: "extension" | "size") => {
|
|
183
258
|
if (reason === "extension") {
|
|
@@ -192,7 +267,7 @@ const handleInvalidFile = (file: File, reason: "extension" | "size") => {
|
|
|
192
267
|
onFileSelect={setFile}
|
|
193
268
|
onInvalidFile={handleInvalidFile}
|
|
194
269
|
invalid={!!error}
|
|
195
|
-
|
|
270
|
+
/>;
|
|
196
271
|
```
|
|
197
272
|
|
|
198
273
|
### Modo Múltiple
|
|
@@ -236,10 +311,12 @@ filesPosition?: "above" | "below"
|
|
|
236
311
|
```
|
|
237
312
|
|
|
238
313
|
Posición donde aparecen los archivos seleccionados en modo múltiple:
|
|
314
|
+
|
|
239
315
|
- `"above"` - Archivos arriba del área de drag & drop
|
|
240
316
|
- `"below"` - Archivos debajo del área de drag & drop (por defecto)
|
|
241
317
|
|
|
242
318
|
**Ejemplo**:
|
|
319
|
+
|
|
243
320
|
```tsx
|
|
244
321
|
<FileUpload
|
|
245
322
|
selectedFiles={files}
|
|
@@ -265,7 +342,8 @@ type FileUploadLabels = {
|
|
|
265
342
|
Etiquetas personalizables para internacionalización o textos específicos.
|
|
266
343
|
|
|
267
344
|
**Ejemplo**:
|
|
268
|
-
|
|
345
|
+
|
|
346
|
+
````tsx
|
|
269
347
|
<FileUpload
|
|
270
348
|
selectedFile={file}
|
|
271
349
|
onFileSelect={setFile}
|
|
@@ -291,11 +369,12 @@ Etiquetas personalizables para internacionalización o textos específicos.
|
|
|
291
369
|
|
|
292
370
|
```tsx
|
|
293
371
|
acceptedExtensions?: string[]
|
|
294
|
-
|
|
372
|
+
````
|
|
295
373
|
|
|
296
374
|
Array de extensiones de archivo permitidas. Por defecto: `[".xls", ".xlsx", ".numbers"]`
|
|
297
375
|
|
|
298
376
|
**Ejemplo**:
|
|
377
|
+
|
|
299
378
|
```tsx
|
|
300
379
|
<FileUpload
|
|
301
380
|
selectedFile={file}
|
|
@@ -313,12 +392,9 @@ maxSizeInMB?: number
|
|
|
313
392
|
Tamaño máximo del archivo en megabytes. Por defecto: `50`
|
|
314
393
|
|
|
315
394
|
**Ejemplo**:
|
|
395
|
+
|
|
316
396
|
```tsx
|
|
317
|
-
<FileUpload
|
|
318
|
-
selectedFile={file}
|
|
319
|
-
onFileSelect={setFile}
|
|
320
|
-
maxSizeInMB={10}
|
|
321
|
-
/>
|
|
397
|
+
<FileUpload selectedFile={file} onFileSelect={setFile} maxSizeInMB={10} />
|
|
322
398
|
```
|
|
323
399
|
|
|
324
400
|
### labels
|
|
@@ -336,6 +412,7 @@ type FileUploadLabels = {
|
|
|
336
412
|
Etiquetas personalizables para internacionalización o textos específicos.
|
|
337
413
|
|
|
338
414
|
**Ejemplo**:
|
|
415
|
+
|
|
339
416
|
```tsx
|
|
340
417
|
<FileUpload
|
|
341
418
|
selectedFile={file}
|
|
@@ -343,16 +420,185 @@ Etiquetas personalizables para internacionalización o textos específicos.
|
|
|
343
420
|
labels={{
|
|
344
421
|
dragDrop: "Arrastra y suelta tu archivo aquí o",
|
|
345
422
|
selectFile: "Selecciona el archivo",
|
|
346
|
-
fileRequirements: "Archivos permitidos: .xls, .xlsx. Tamaño máximo 50 MB."
|
|
423
|
+
fileRequirements: "Archivos permitidos: .xls, .xlsx. Tamaño máximo 50 MB.",
|
|
347
424
|
}}
|
|
348
425
|
/>
|
|
349
426
|
```
|
|
350
427
|
|
|
351
428
|
## Ejemplos
|
|
352
429
|
|
|
353
|
-
###
|
|
430
|
+
### Con Componentes Field
|
|
431
|
+
|
|
432
|
+
```tsx
|
|
433
|
+
import {
|
|
434
|
+
Field,
|
|
435
|
+
FieldLabel,
|
|
436
|
+
FieldDescription,
|
|
437
|
+
FieldError,
|
|
438
|
+
FieldGroup,
|
|
439
|
+
} from "@adamosuiteservices/ui/field";
|
|
440
|
+
import { FileUpload } from "@adamosuiteservices/ui/file-upload";
|
|
441
|
+
|
|
442
|
+
function FileUploadForm() {
|
|
443
|
+
const [file, setFile] = useState<File | null>(null);
|
|
444
|
+
const [error, setError] = useState<string>("");
|
|
445
|
+
|
|
446
|
+
const handleInvalidFile = (file: File, reason: "extension" | "size") => {
|
|
447
|
+
if (reason === "extension") {
|
|
448
|
+
setError(`File "${file.name}" has an invalid extension.`);
|
|
449
|
+
} else if (reason === "size") {
|
|
450
|
+
setError(`File "${file.name}" exceeds the maximum size limit.`);
|
|
451
|
+
}
|
|
452
|
+
};
|
|
453
|
+
|
|
454
|
+
return (
|
|
455
|
+
<FieldGroup className="adm:max-w-xl">
|
|
456
|
+
<Field>
|
|
457
|
+
<FieldLabel>Upload document (PDF or DOCX only, max 2MB)</FieldLabel>
|
|
458
|
+
<FileUpload
|
|
459
|
+
selectedFile={file}
|
|
460
|
+
onFileSelect={(newFile) => {
|
|
461
|
+
setFile(newFile);
|
|
462
|
+
if (newFile) setError("");
|
|
463
|
+
}}
|
|
464
|
+
onInvalidFile={handleInvalidFile}
|
|
465
|
+
aria-invalid={!!error}
|
|
466
|
+
acceptedExtensions={[".pdf", ".docx"]}
|
|
467
|
+
maxSizeInMB={2}
|
|
468
|
+
/>
|
|
469
|
+
{error ? (
|
|
470
|
+
<FieldError>{error}</FieldError>
|
|
471
|
+
) : (
|
|
472
|
+
<FieldDescription>
|
|
473
|
+
Supported formats: PDF, DOCX. Maximum 2MB.
|
|
474
|
+
</FieldDescription>
|
|
475
|
+
)}
|
|
476
|
+
</Field>
|
|
477
|
+
</FieldGroup>
|
|
478
|
+
);
|
|
479
|
+
}
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
### Estado Deshabilitado
|
|
483
|
+
|
|
484
|
+
```tsx
|
|
485
|
+
import {
|
|
486
|
+
Field,
|
|
487
|
+
FieldLabel,
|
|
488
|
+
FieldDescription,
|
|
489
|
+
FieldGroup,
|
|
490
|
+
} from "@adamosuiteservices/ui/field";
|
|
491
|
+
|
|
492
|
+
function DisabledExample() {
|
|
493
|
+
const [file, setFile] = useState<File | null>(null);
|
|
494
|
+
|
|
495
|
+
return (
|
|
496
|
+
<FieldGroup className="adm:max-w-xl">
|
|
497
|
+
<Field>
|
|
498
|
+
<FieldLabel>Upload document (Disabled)</FieldLabel>
|
|
499
|
+
<FileUpload selectedFile={file} onFileSelect={setFile} disabled />
|
|
500
|
+
<FieldDescription>This file upload is disabled</FieldDescription>
|
|
501
|
+
</Field>
|
|
502
|
+
</FieldGroup>
|
|
503
|
+
);
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// Con archivo ya seleccionado
|
|
507
|
+
function DisabledWithFileExample() {
|
|
508
|
+
const mockFile = new File(["content"], "report.xlsx", {
|
|
509
|
+
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
510
|
+
});
|
|
511
|
+
const [file, setFile] = useState<File | null>(mockFile);
|
|
512
|
+
|
|
513
|
+
return (
|
|
514
|
+
<FieldGroup className="adm:max-w-xl">
|
|
515
|
+
<Field>
|
|
516
|
+
<FieldLabel>Upload document (Disabled)</FieldLabel>
|
|
517
|
+
<FileUpload selectedFile={file} onFileSelect={setFile} disabled />
|
|
518
|
+
<FieldDescription>
|
|
519
|
+
File is selected but cannot be removed while disabled
|
|
520
|
+
</FieldDescription>
|
|
521
|
+
</Field>
|
|
522
|
+
</FieldGroup>
|
|
523
|
+
);
|
|
524
|
+
}
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
### Dentro de Fieldset Disabled
|
|
528
|
+
|
|
529
|
+
```tsx
|
|
530
|
+
import {
|
|
531
|
+
FieldSet,
|
|
532
|
+
FieldLegend,
|
|
533
|
+
FieldGroup,
|
|
534
|
+
Field,
|
|
535
|
+
FieldLabel,
|
|
536
|
+
} from "@adamosuiteservices/ui/field";
|
|
537
|
+
|
|
538
|
+
function FieldsetExample() {
|
|
539
|
+
const [file, setFile] = useState<File | null>(null);
|
|
540
|
+
|
|
541
|
+
return (
|
|
542
|
+
<FieldSet disabled>
|
|
543
|
+
<FieldLegend>Document upload (Fieldset disabled)</FieldLegend>
|
|
544
|
+
<FieldGroup>
|
|
545
|
+
<Field>
|
|
546
|
+
<FieldLabel>Upload document</FieldLabel>
|
|
547
|
+
<FileUpload
|
|
548
|
+
selectedFile={file}
|
|
549
|
+
onFileSelect={setFile}
|
|
550
|
+
className="adm:max-w-xl"
|
|
551
|
+
/>
|
|
552
|
+
</Field>
|
|
553
|
+
</FieldGroup>
|
|
554
|
+
</FieldSet>
|
|
555
|
+
);
|
|
556
|
+
}
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
**Nota**: El componente detecta automáticamente cuando está dentro de un `<fieldset disabled>` o `<FieldSet disabled>` y se deshabilita automáticamente.
|
|
560
|
+
|
|
561
|
+
### Con Props de Input Personalizadas
|
|
354
562
|
|
|
355
563
|
```tsx
|
|
564
|
+
import {
|
|
565
|
+
Field,
|
|
566
|
+
FieldLabel,
|
|
567
|
+
FieldDescription,
|
|
568
|
+
FieldGroup,
|
|
569
|
+
} from "@adamosuiteservices/ui/field";
|
|
570
|
+
|
|
571
|
+
function CustomInputPropsExample() {
|
|
572
|
+
const [file, setFile] = useState<File | null>(null);
|
|
573
|
+
|
|
574
|
+
return (
|
|
575
|
+
<FieldGroup className="adm:max-w-xl">
|
|
576
|
+
<Field>
|
|
577
|
+
<FieldLabel htmlFor="custom-document-upload">
|
|
578
|
+
Upload document
|
|
579
|
+
</FieldLabel>
|
|
580
|
+
<FileUpload
|
|
581
|
+
selectedFile={file}
|
|
582
|
+
onFileSelect={setFile}
|
|
583
|
+
input={{
|
|
584
|
+
id: "custom-document-upload",
|
|
585
|
+
name: "document",
|
|
586
|
+
"data-testid": "file-input",
|
|
587
|
+
"aria-describedby": "upload-description",
|
|
588
|
+
}}
|
|
589
|
+
/>
|
|
590
|
+
<FieldDescription id="upload-description">
|
|
591
|
+
Custom input props allow you to set ID, name, and other attributes
|
|
592
|
+
</FieldDescription>
|
|
593
|
+
</Field>
|
|
594
|
+
</FieldGroup>
|
|
595
|
+
);
|
|
596
|
+
}
|
|
597
|
+
```
|
|
598
|
+
|
|
599
|
+
### Archivos de Excel (Por Defecto)
|
|
600
|
+
|
|
601
|
+
````tsx
|
|
356
602
|
const [file, setFile] = useState<File | null>(null);
|
|
357
603
|
|
|
358
604
|
<FileUpload
|
|
@@ -372,7 +618,7 @@ const [files, setFiles] = useState<File[]>([]);
|
|
|
372
618
|
selectFile: "Select files"
|
|
373
619
|
}}
|
|
374
620
|
/>
|
|
375
|
-
|
|
621
|
+
````
|
|
376
622
|
|
|
377
623
|
### Múltiples Imágenes
|
|
378
624
|
|
|
@@ -389,10 +635,12 @@ const [images, setImages] = useState<File[]>([]);
|
|
|
389
635
|
labels={{
|
|
390
636
|
dragDrop: "Drag and drop your images here or",
|
|
391
637
|
selectFile: "Select images",
|
|
392
|
-
fileRequirements:
|
|
393
|
-
|
|
638
|
+
fileRequirements:
|
|
639
|
+
"Supported formats: JPG, PNG, GIF, WebP. Max 5MB each. Up to 10 images.",
|
|
640
|
+
filesSelected: (count) =>
|
|
641
|
+
`${count} image${count !== 1 ? "s" : ""} selected`,
|
|
394
642
|
}}
|
|
395
|
-
|
|
643
|
+
/>;
|
|
396
644
|
```
|
|
397
645
|
|
|
398
646
|
### Archivos Arriba del Área de Drop
|
|
@@ -407,14 +655,14 @@ const [files, setFiles] = useState<File[]>([]);
|
|
|
407
655
|
filesPosition="above"
|
|
408
656
|
acceptedExtensions={[".pdf"]}
|
|
409
657
|
maxSizeInMB={10}
|
|
410
|
-
|
|
658
|
+
/>;
|
|
411
659
|
```
|
|
660
|
+
|
|
412
661
|
/>
|
|
413
|
-
```
|
|
414
662
|
|
|
415
663
|
### Imágenes
|
|
416
664
|
|
|
417
|
-
|
|
665
|
+
````tsx
|
|
418
666
|
const [image, setImage] = useState<File | null>(null);
|
|
419
667
|
|
|
420
668
|
## Estados Visuales
|
|
@@ -452,7 +700,7 @@ Cuando el usuario arrastra archivos sobre el área:
|
|
|
452
700
|
|
|
453
701
|
### Estado Inválido
|
|
454
702
|
|
|
455
|
-
Cuando `invalid={true}`:
|
|
703
|
+
Cuando `invalid={true}` o `aria-invalid={true}`:
|
|
456
704
|
- Borde: `border-destructive`
|
|
457
705
|
- Fondo: `bg-destructive/5`
|
|
458
706
|
- Icono de documento: `text-destructive` con fondo `bg-destructive/10`
|
|
@@ -460,16 +708,22 @@ Cuando `invalid={true}`:
|
|
|
460
708
|
- Contador de archivos: `text-destructive`
|
|
461
709
|
- Tarjetas de archivos: borde y fondo con colores destructivos
|
|
462
710
|
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
711
|
+
### Estado Deshabilitado
|
|
712
|
+
|
|
713
|
+
Cuando `disabled={true}` o está dentro de `<fieldset disabled>`:
|
|
714
|
+
|
|
715
|
+
**Área de drag & drop**:
|
|
716
|
+
- Opacidad: `opacity-50`
|
|
717
|
+
- Cursor: `cursor-not-allowed`
|
|
718
|
+
- Los estados de error visual no se aplican
|
|
719
|
+
|
|
720
|
+
**Archivos seleccionados**:
|
|
721
|
+
- Opacidad: `opacity-50`
|
|
722
|
+
- Cursor: `cursor-not-allowed`
|
|
723
|
+
- Botón de eliminar deshabilitado
|
|
724
|
+
- Botón "Clear all" deshabilitado (modo múltiple)
|
|
725
|
+
- Los colores destructivos no se muestran aunque `invalid={true}`
|
|
726
|
+
|
|
473
727
|
## Validación
|
|
474
728
|
|
|
475
729
|
El componente valida automáticamente:
|
|
@@ -518,7 +772,7 @@ const handleInvalidFile = (file: File, reason: "extension" | "size") => {
|
|
|
518
772
|
</Typography>
|
|
519
773
|
</div>
|
|
520
774
|
)}
|
|
521
|
-
|
|
775
|
+
````
|
|
522
776
|
|
|
523
777
|
## Comportamiento por Modo
|
|
524
778
|
|
|
@@ -558,9 +812,9 @@ const [archive, setArchive] = useState<File | null>(null);
|
|
|
558
812
|
maxSizeInMB={500}
|
|
559
813
|
labels={{
|
|
560
814
|
dragDrop: "Drag and drop your archive here or",
|
|
561
|
-
selectFile: "Select the archive"
|
|
815
|
+
selectFile: "Select the archive",
|
|
562
816
|
}}
|
|
563
|
-
|
|
817
|
+
/>;
|
|
564
818
|
```
|
|
565
819
|
|
|
566
820
|
### Con Integración en Formulario
|
|
@@ -571,7 +825,7 @@ function UploadForm() {
|
|
|
571
825
|
|
|
572
826
|
const handleSubmit = async (e: React.FormEvent) => {
|
|
573
827
|
e.preventDefault();
|
|
574
|
-
|
|
828
|
+
|
|
575
829
|
if (!file) return;
|
|
576
830
|
|
|
577
831
|
const formData = new FormData();
|
|
@@ -582,7 +836,7 @@ function UploadForm() {
|
|
|
582
836
|
method: "POST",
|
|
583
837
|
body: formData
|
|
584
838
|
});
|
|
585
|
-
|
|
839
|
+
|
|
586
840
|
### Botón de Remover
|
|
587
841
|
|
|
588
842
|
- Variante: `destructive-medium`
|
|
@@ -617,6 +871,7 @@ function UploadForm() {
|
|
|
617
871
|
### Sin Archivo Seleccionado
|
|
618
872
|
|
|
619
873
|
El componente muestra:
|
|
874
|
+
|
|
620
875
|
- Área de drag & drop con borde punteado
|
|
621
876
|
- Icono de documento en fondo azul claro
|
|
622
877
|
- Texto instructivo y link para seleccionar archivo
|
|
@@ -625,6 +880,7 @@ El componente muestra:
|
|
|
625
880
|
### Durante Drag Over
|
|
626
881
|
|
|
627
882
|
Cuando el usuario arrastra un archivo sobre el área:
|
|
883
|
+
|
|
628
884
|
- Borde cambia a `border-primary-500`
|
|
629
885
|
- Fondo cambia a `bg-primary-50`
|
|
630
886
|
- Transición suave con `transition-colors`
|
|
@@ -632,6 +888,7 @@ Cuando el usuario arrastra un archivo sobre el área:
|
|
|
632
888
|
### Con Archivo Seleccionado
|
|
633
889
|
|
|
634
890
|
El componente muestra:
|
|
891
|
+
|
|
635
892
|
- Tarjeta con fondo `bg-neutral-100`
|
|
636
893
|
- Icono de documento en fondo blanco
|
|
637
894
|
- Nombre del archivo (con truncate si es largo)
|
|
@@ -687,9 +944,10 @@ border-primary-500 bg-primary-50
|
|
|
687
944
|
- Label asociado correctamente con `htmlFor="file-upload"`
|
|
688
945
|
- Botón de link usa `asChild` para comportamiento semántico
|
|
689
946
|
- Typography con color `muted` para textos secundarios
|
|
947
|
+
|
|
690
948
|
## Tipos TypeScript
|
|
691
949
|
|
|
692
|
-
|
|
950
|
+
````typescript
|
|
693
951
|
export type FileUploadLabels = {
|
|
694
952
|
dragDrop?: string
|
|
695
953
|
selectFile?: string
|
|
@@ -701,22 +959,28 @@ export type FileUploadProps = ComponentProps<"div"> & Readonly<{
|
|
|
701
959
|
// Modo simple
|
|
702
960
|
selectedFile?: File | null
|
|
703
961
|
onFileSelect?: (file: File | null) => void
|
|
704
|
-
|
|
962
|
+
|
|
705
963
|
// Modo múltiple
|
|
706
964
|
selectedFiles?: File[]
|
|
707
965
|
onFilesSelect?: (files: File[]) => void
|
|
708
966
|
multiple?: boolean
|
|
709
967
|
maxFiles?: number
|
|
710
968
|
filesPosition?: "above" | "below"
|
|
711
|
-
|
|
969
|
+
|
|
712
970
|
// Validación
|
|
713
971
|
onInvalidFile?: (file: File, reason: "extension" | "size") => void
|
|
714
972
|
invalid?: boolean
|
|
715
|
-
|
|
973
|
+
"aria-invalid"?: boolean
|
|
974
|
+
|
|
975
|
+
// Estados
|
|
976
|
+
disabled?: boolean
|
|
977
|
+
|
|
716
978
|
// Común
|
|
717
979
|
acceptedExtensions?: string[]
|
|
718
980
|
maxSizeInMB?: number
|
|
719
981
|
labels?: FileUploadLabels
|
|
982
|
+
input?: ComponentProps<"input">
|
|
983
|
+
}>
|
|
720
984
|
### FormData para Upload
|
|
721
985
|
|
|
722
986
|
```tsx
|
|
@@ -750,7 +1014,8 @@ const uploadFiles = async (files: File[]) => {
|
|
|
750
1014
|
|
|
751
1015
|
return response.json();
|
|
752
1016
|
};
|
|
753
|
-
|
|
1017
|
+
````
|
|
1018
|
+
|
|
754
1019
|
```tsx
|
|
755
1020
|
// No validar el archivo antes de hacer upload
|
|
756
1021
|
// Siempre verificar que file no sea null
|
|
@@ -769,18 +1034,19 @@ labels={{ selectFile: "Select file" }} // Poco descriptivo
|
|
|
769
1034
|
|
|
770
1035
|
```typescript
|
|
771
1036
|
export type FileUploadLabels = {
|
|
772
|
-
dragDrop?: string
|
|
773
|
-
selectFile?: string
|
|
774
|
-
fileRequirements?: string
|
|
1037
|
+
dragDrop?: string;
|
|
1038
|
+
selectFile?: string;
|
|
1039
|
+
fileRequirements?: string;
|
|
775
1040
|
};
|
|
776
1041
|
|
|
777
|
-
export type FileUploadProps = ComponentProps<"div"> &
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
1042
|
+
export type FileUploadProps = ComponentProps<"div"> &
|
|
1043
|
+
Readonly<{
|
|
1044
|
+
selectedFile: File | null;
|
|
1045
|
+
onFileSelect: (file: File | null) => void;
|
|
1046
|
+
acceptedExtensions?: string[];
|
|
1047
|
+
maxSizeInMB?: number;
|
|
1048
|
+
labels?: FileUploadLabels;
|
|
1049
|
+
}>;
|
|
784
1050
|
```
|
|
785
1051
|
|
|
786
1052
|
## Integración con Backend
|
|
@@ -791,13 +1057,16 @@ export type FileUploadProps = ComponentProps<"div"> & Readonly<{
|
|
|
791
1057
|
const uploadFile = async (file: File) => {
|
|
792
1058
|
const formData = new FormData();
|
|
793
1059
|
formData.append("file", file);
|
|
794
|
-
formData.append(
|
|
795
|
-
|
|
796
|
-
|
|
1060
|
+
formData.append(
|
|
1061
|
+
"metadata",
|
|
1062
|
+
JSON.stringify({
|
|
1063
|
+
uploadedAt: new Date().toISOString(),
|
|
1064
|
+
}),
|
|
1065
|
+
);
|
|
797
1066
|
|
|
798
1067
|
const response = await fetch("/api/upload", {
|
|
799
1068
|
method: "POST",
|
|
800
|
-
body: formData
|
|
1069
|
+
body: formData,
|
|
801
1070
|
});
|
|
802
1071
|
|
|
803
1072
|
return response.json();
|
|
@@ -806,7 +1075,7 @@ const uploadFile = async (file: File) => {
|
|
|
806
1075
|
|
|
807
1076
|
### Con Progress Tracking
|
|
808
1077
|
|
|
809
|
-
|
|
1078
|
+
````tsx
|
|
810
1079
|
const [progress, setProgress] = useState(0);
|
|
811
1080
|
|
|
812
1081
|
const uploadWithProgress = async (file: File) => {
|
|
@@ -826,6 +1095,9 @@ const uploadWithProgress = async (file: File) => {
|
|
|
826
1095
|
| Posición de preview | ❌ No configurable | ✅ Arriba o abajo |
|
|
827
1096
|
| UX consistente | ❌ Varía por browser | ✅ Consistente |
|
|
828
1097
|
| Labels customizables | ❌ No | ✅ Sí (i18n friendly) |
|
|
1098
|
+
| aria-invalid | ⚠️ Manual | ✅ Integrado |
|
|
1099
|
+
| Fieldset disabled | ⚠️ Nativo pero sin estilos | ✅ Detectado automáticamente |
|
|
1100
|
+
| Archivos deshabilitados | ❌ No soportado | ✅ Visual feedback completo |
|
|
829
1101
|
|
|
830
1102
|
## Notas
|
|
831
1103
|
|
|
@@ -836,42 +1108,39 @@ const uploadWithProgress = async (file: File) => {
|
|
|
836
1108
|
- Los archivos no se almacenan automáticamente - manejar el upload con los callbacks
|
|
837
1109
|
- El componente es completamente controlado - el padre maneja el estado de los archivos
|
|
838
1110
|
- El input file es reutilizable: después de seleccionar, se resetea para permitir seleccionar el mismo archivo de nuevo
|
|
1111
|
+
- **Generación automática de ID**: Si no se proporciona un `id` en la prop `input`, se genera automáticamente con formato `file-upload-{random}` para garantizar accesibilidad
|
|
1112
|
+
- **Detección automática de fieldset**: El componente detecta cuando está dentro de un `<fieldset disabled>` usando MutationObserver y se deshabilita automáticamente
|
|
1113
|
+
- **Archivos seleccionados y disabled**: Cuando el componente está deshabilitado, los archivos seleccionados se muestran visualmente deshabilitados y no pueden ser removidos
|
|
1114
|
+
- Usar con los componentes Field (`Field`, `FieldLabel`, `FieldError`, etc.) para una mejor experiencia de formulario
|
|
839
1115
|
```tsx
|
|
840
1116
|
<FileUpload
|
|
841
1117
|
selectedFile={file}
|
|
842
1118
|
onFileSelect={setFile}
|
|
843
1119
|
className="adm:max-w-2xl adm:mx-auto"
|
|
844
1120
|
/>
|
|
845
|
-
|
|
1121
|
+
````
|
|
846
1122
|
|
|
847
1123
|
### Con Wrapper Adicional
|
|
848
1124
|
|
|
849
1125
|
```tsx
|
|
850
1126
|
<div className="adm:space-y-4">
|
|
851
1127
|
<Label>Upload your document</Label>
|
|
852
|
-
<FileUpload
|
|
853
|
-
|
|
854
|
-
onFileSelect={setFile}
|
|
855
|
-
/>
|
|
856
|
-
{file && (
|
|
857
|
-
<Typography color="success">
|
|
858
|
-
✓ File ready to upload
|
|
859
|
-
</Typography>
|
|
860
|
-
)}
|
|
1128
|
+
<FileUpload selectedFile={file} onFileSelect={setFile} />
|
|
1129
|
+
{file && <Typography color="success">✓ File ready to upload</Typography>}
|
|
861
1130
|
</div>
|
|
862
1131
|
```
|
|
863
1132
|
|
|
864
1133
|
## Comparación con Input type="file"
|
|
865
1134
|
|
|
866
|
-
| Característica
|
|
867
|
-
|
|
868
|
-
| Drag & Drop
|
|
869
|
-
| Validación visual
|
|
870
|
-
| Preview del archivo
|
|
871
|
-
| Extensiones validadas | ⚠️ Solo accept attribute | ✅ Con feedback visual
|
|
872
|
-
| Tamaño validado
|
|
873
|
-
| UX consistente
|
|
874
|
-
| Labels customizables
|
|
1135
|
+
| Característica | Input file nativo | FileUpload |
|
|
1136
|
+
| --------------------- | ------------------------ | ------------------------- |
|
|
1137
|
+
| Drag & Drop | ❌ No | ✅ Sí |
|
|
1138
|
+
| Validación visual | ❌ No | ✅ Sí |
|
|
1139
|
+
| Preview del archivo | ❌ No | ✅ Sí con nombre y tamaño |
|
|
1140
|
+
| Extensiones validadas | ⚠️ Solo accept attribute | ✅ Con feedback visual |
|
|
1141
|
+
| Tamaño validado | ❌ Solo en backend | ✅ Cliente y servidor |
|
|
1142
|
+
| UX consistente | ❌ Varía por browser | ✅ Consistente |
|
|
1143
|
+
| Labels customizables | ❌ No | ✅ Sí (i18n friendly) |
|
|
875
1144
|
|
|
876
1145
|
## Notas
|
|
877
1146
|
|