@adamosuiteservices/ui 2.13.0 → 2.13.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.
@@ -0,0 +1,473 @@
1
+ # File Upload Component
2
+
3
+ ## Descripción
4
+
5
+ Componente de carga de archivos con funcionalidad **drag & drop**, validación de extensiones y tamaño, vista previa del archivo seleccionado y área de arrastre visual. Ideal para formularios que requieren subida de archivos con feedback inmediato al usuario.
6
+
7
+ ## Características
8
+
9
+ - ✅ Drag & Drop con feedback visual
10
+ - ✅ Validación de extensiones de archivo
11
+ - ✅ Validación de tamaño máximo
12
+ - ✅ Vista previa del archivo seleccionado con tamaño
13
+ - ✅ Botón para remover archivo seleccionado
14
+ - ✅ Etiquetas personalizables (i18n)
15
+ - ✅ Estado de arrastre visual (borde y fondo cambian)
16
+ - ✅ Integración con formularios React
17
+ - ✅ TypeScript completo con tipos exportados
18
+
19
+ ## Importación
20
+
21
+ ```typescript
22
+ import { FileUpload } from "@adamosuiteservices/ui/file-upload";
23
+ import type { FileUploadProps, FileUploadLabels } from "@adamosuiteservices/ui/file-upload";
24
+ ```
25
+
26
+ ## Uso Básico
27
+
28
+ ```tsx
29
+ import { useState } from "react";
30
+ import { FileUpload } from "@adamosuiteservices/ui/file-upload";
31
+
32
+ function MyForm() {
33
+ const [file, setFile] = useState<File | null>(null);
34
+
35
+ return (
36
+ <FileUpload
37
+ selectedFile={file}
38
+ onFileSelect={setFile}
39
+ />
40
+ );
41
+ }
42
+ ```
43
+
44
+ **Configuración por defecto**:
45
+ - Extensiones aceptadas: `.xls`, `.xlsx`, `.numbers`
46
+ - Tamaño máximo: 50 MB
47
+
48
+ ## Props
49
+
50
+ ### selectedFile (requerido)
51
+
52
+ ```tsx
53
+ selectedFile: File | null
54
+ ```
55
+
56
+ Estado del archivo actualmente seleccionado. Usa `null` cuando no hay archivo.
57
+
58
+ ### onFileSelect (requerido)
59
+
60
+ ```tsx
61
+ onFileSelect: (file: File | null) => void
62
+ ```
63
+
64
+ Callback invocado cuando el usuario selecciona o remueve un archivo.
65
+
66
+ ### acceptedExtensions
67
+
68
+ ```tsx
69
+ acceptedExtensions?: string[]
70
+ ```
71
+
72
+ Array de extensiones de archivo permitidas. Por defecto: `[".xls", ".xlsx", ".numbers"]`
73
+
74
+ **Ejemplo**:
75
+ ```tsx
76
+ <FileUpload
77
+ selectedFile={file}
78
+ onFileSelect={setFile}
79
+ acceptedExtensions={[".pdf", ".doc", ".docx"]}
80
+ />
81
+ ```
82
+
83
+ ### maxSizeInMB
84
+
85
+ ```tsx
86
+ maxSizeInMB?: number
87
+ ```
88
+
89
+ Tamaño máximo del archivo en megabytes. Por defecto: `50`
90
+
91
+ **Ejemplo**:
92
+ ```tsx
93
+ <FileUpload
94
+ selectedFile={file}
95
+ onFileSelect={setFile}
96
+ maxSizeInMB={10}
97
+ />
98
+ ```
99
+
100
+ ### labels
101
+
102
+ ```tsx
103
+ labels?: FileUploadLabels
104
+
105
+ type FileUploadLabels = {
106
+ dragDrop?: string
107
+ selectFile?: string
108
+ fileRequirements?: string
109
+ }
110
+ ```
111
+
112
+ Etiquetas personalizables para internacionalización o textos específicos.
113
+
114
+ **Ejemplo**:
115
+ ```tsx
116
+ <FileUpload
117
+ selectedFile={file}
118
+ onFileSelect={setFile}
119
+ labels={{
120
+ dragDrop: "Arrastra y suelta tu archivo aquí o",
121
+ selectFile: "Selecciona el archivo",
122
+ fileRequirements: "Archivos permitidos: .xls, .xlsx. Tamaño máximo 50 MB."
123
+ }}
124
+ />
125
+ ```
126
+
127
+ ## Ejemplos
128
+
129
+ ### Archivos de Excel (Por Defecto)
130
+
131
+ ```tsx
132
+ const [file, setFile] = useState<File | null>(null);
133
+
134
+ <FileUpload
135
+ selectedFile={file}
136
+ onFileSelect={setFile}
137
+ />
138
+ ```
139
+
140
+ ### Imágenes
141
+
142
+ ```tsx
143
+ const [image, setImage] = useState<File | null>(null);
144
+
145
+ <FileUpload
146
+ selectedFile={image}
147
+ onFileSelect={setImage}
148
+ acceptedExtensions={[".jpg", ".jpeg", ".png", ".gif", ".webp"]}
149
+ maxSizeInMB={5}
150
+ labels={{
151
+ dragDrop: "Drag and drop your image here or",
152
+ selectFile: "Select an image",
153
+ fileRequirements: "Supported formats: JPG, PNG, GIF, WebP. Max 5MB."
154
+ }}
155
+ />
156
+ ```
157
+
158
+ ### Documentos PDF
159
+
160
+ ```tsx
161
+ const [document, setDocument] = useState<File | null>(null);
162
+
163
+ <FileUpload
164
+ selectedFile={document}
165
+ onFileSelect={setDocument}
166
+ acceptedExtensions={[".pdf"]}
167
+ maxSizeInMB={20}
168
+ labels={{
169
+ dragDrop: "Drag and drop your PDF here or",
170
+ selectFile: "Select PDF",
171
+ fileRequirements: "Only PDF files. Maximum 20 MB."
172
+ }}
173
+ />
174
+ ```
175
+
176
+ ### CSV/Excel para Importación
177
+
178
+ ```tsx
179
+ const [dataFile, setDataFile] = useState<File | null>(null);
180
+
181
+ <FileUpload
182
+ selectedFile={dataFile}
183
+ onFileSelect={setDataFile}
184
+ acceptedExtensions={[".csv", ".xls", ".xlsx"]}
185
+ maxSizeInMB={30}
186
+ />
187
+ ```
188
+
189
+ ### Archivos Comprimidos
190
+
191
+ ```tsx
192
+ const [archive, setArchive] = useState<File | null>(null);
193
+
194
+ <FileUpload
195
+ selectedFile={archive}
196
+ onFileSelect={setArchive}
197
+ acceptedExtensions={[".zip", ".rar", ".7z"]}
198
+ maxSizeInMB={500}
199
+ labels={{
200
+ dragDrop: "Drag and drop your archive here or",
201
+ selectFile: "Select the archive"
202
+ }}
203
+ />
204
+ ```
205
+
206
+ ### Con Integración en Formulario
207
+
208
+ ```tsx
209
+ function UploadForm() {
210
+ const [file, setFile] = useState<File | null>(null);
211
+
212
+ const handleSubmit = async (e: React.FormEvent) => {
213
+ e.preventDefault();
214
+
215
+ if (!file) return;
216
+
217
+ const formData = new FormData();
218
+ formData.append("file", file);
219
+
220
+ try {
221
+ const response = await fetch("/api/upload", {
222
+ method: "POST",
223
+ body: formData
224
+ });
225
+
226
+ if (response.ok) {
227
+ alert("File uploaded successfully!");
228
+ setFile(null);
229
+ }
230
+ } catch (error) {
231
+ console.error("Upload failed:", error);
232
+ }
233
+ };
234
+
235
+ return (
236
+ <form onSubmit={handleSubmit} className="space-y-4">
237
+ <FileUpload
238
+ selectedFile={file}
239
+ onFileSelect={setFile}
240
+ />
241
+ <Button type="submit" disabled={!file}>
242
+ Upload File
243
+ </Button>
244
+ </form>
245
+ );
246
+ }
247
+ ```
248
+
249
+ ## Estados Visuales
250
+
251
+ ### Sin Archivo Seleccionado
252
+
253
+ El componente muestra:
254
+ - Área de drag & drop con borde punteado
255
+ - Icono de documento en fondo azul claro
256
+ - Texto instructivo y link para seleccionar archivo
257
+ - Requisitos del archivo (extensiones y tamaño)
258
+
259
+ ### Durante Drag Over
260
+
261
+ Cuando el usuario arrastra un archivo sobre el área:
262
+ - Borde cambia a `border-primary-500`
263
+ - Fondo cambia a `bg-primary-50`
264
+ - Transición suave con `transition-colors`
265
+
266
+ ### Con Archivo Seleccionado
267
+
268
+ El componente muestra:
269
+ - Tarjeta con fondo `bg-neutral-100`
270
+ - Icono de documento en fondo blanco
271
+ - Nombre del archivo (con truncate si es largo)
272
+ - Tamaño del archivo en MB
273
+ - Botón destructivo para remover el archivo
274
+
275
+ ## Validación
276
+
277
+ El componente valida automáticamente:
278
+
279
+ 1. **Extensión**: Solo acepta archivos con extensiones en `acceptedExtensions`
280
+ 2. **Tamaño**: Rechaza archivos mayores a `maxSizeInMB`
281
+
282
+ Si un archivo no cumple estas validaciones, no se selecciona y `onFileSelect` no se invoca.
283
+
284
+ ## Estilos Base
285
+
286
+ ### Área de Drag & Drop
287
+
288
+ - Padding: `p-6` (24px)
289
+ - Border radius: `rounded-2xl` (16px)
290
+ - Border: `border-2 border-dashed`
291
+ - Gap entre elementos: `gap-6` (24px)
292
+ - Transiciones: `transition-colors`
293
+
294
+ ### Estado Normal vs Dragging
295
+
296
+ ```tsx
297
+ // Normal
298
+ border-neutral-300 bg-neutral-50
299
+
300
+ // Dragging
301
+ border-primary-500 bg-primary-50
302
+ ```
303
+
304
+ ### Tarjeta de Archivo Seleccionado
305
+
306
+ - Padding: `p-6` (24px)
307
+ - Border radius: `rounded-2xl` (16px)
308
+ - Background: `bg-neutral-100`
309
+ - Gap horizontal: `gap-4` (16px)
310
+ - Icono en fondo blanco con `rounded-xl` y `p-2.5`
311
+
312
+ ### Botón de Remover
313
+
314
+ - Variante: `destructive-medium`
315
+ - Icono: `delete` de Material Symbols
316
+ - Color del icono: `text-destructive-500`
317
+
318
+ ## Accesibilidad
319
+
320
+ - Input file oculto con `className="adm:hidden"`
321
+ - Label asociado correctamente con `htmlFor="file-upload"`
322
+ - Botón de link usa `asChild` para comportamiento semántico
323
+ - Typography con color `muted` para textos secundarios
324
+
325
+ ## Buenas Prácticas
326
+
327
+ ### ✅ Recomendado
328
+
329
+ ```tsx
330
+ // Estado controlado con useState
331
+ const [file, setFile] = useState<File | null>(null);
332
+
333
+ // Validación de tamaño apropiada
334
+ maxSizeInMB={10} // Para documentos comunes
335
+ maxSizeInMB={5} // Para imágenes
336
+ maxSizeInMB={500} // Para archivos grandes (zip, videos)
337
+
338
+ // Labels específicas y claras
339
+ labels={{
340
+ dragDrop: "Drag and drop your invoice here or",
341
+ selectFile: "Select invoice",
342
+ fileRequirements: "PDF only. Max 10MB."
343
+ }}
344
+
345
+ // Reset después de upload exitoso
346
+ onSuccess={() => setFile(null)}
347
+ ```
348
+
349
+ ### ❌ Evitar
350
+
351
+ ```tsx
352
+ // No validar el archivo antes de hacer upload
353
+ // Siempre verificar que file no sea null
354
+
355
+ // Extensiones demasiado permisivas
356
+ acceptedExtensions={[".*"]} // Inseguro
357
+
358
+ // Tamaño excesivo sin justificación
359
+ maxSizeInMB={10000} // 10GB es excesivo
360
+
361
+ // Labels genéricas cuando el contexto es específico
362
+ labels={{ selectFile: "Select file" }} // Poco descriptivo
363
+ ```
364
+
365
+ ## Tipos TypeScript
366
+
367
+ ```typescript
368
+ export type FileUploadLabels = {
369
+ dragDrop?: string
370
+ selectFile?: string
371
+ fileRequirements?: string
372
+ };
373
+
374
+ export type FileUploadProps = ComponentProps<"div"> & Readonly<{
375
+ selectedFile: File | null
376
+ onFileSelect: (file: File | null) => void
377
+ acceptedExtensions?: string[]
378
+ maxSizeInMB?: number
379
+ labels?: FileUploadLabels
380
+ }>;
381
+ ```
382
+
383
+ ## Integración con Backend
384
+
385
+ ### FormData para Upload
386
+
387
+ ```tsx
388
+ const uploadFile = async (file: File) => {
389
+ const formData = new FormData();
390
+ formData.append("file", file);
391
+ formData.append("metadata", JSON.stringify({
392
+ uploadedAt: new Date().toISOString()
393
+ }));
394
+
395
+ const response = await fetch("/api/upload", {
396
+ method: "POST",
397
+ body: formData
398
+ });
399
+
400
+ return response.json();
401
+ };
402
+ ```
403
+
404
+ ### Con Progress Tracking
405
+
406
+ ```tsx
407
+ const [progress, setProgress] = useState(0);
408
+
409
+ const uploadWithProgress = async (file: File) => {
410
+ const formData = new FormData();
411
+ formData.append("file", file);
412
+
413
+ const xhr = new XMLHttpRequest();
414
+
415
+ xhr.upload.addEventListener("progress", (e) => {
416
+ if (e.lengthComputable) {
417
+ setProgress((e.loaded / e.total) * 100);
418
+ }
419
+ });
420
+
421
+ xhr.open("POST", "/api/upload");
422
+ xhr.send(formData);
423
+ };
424
+ ```
425
+
426
+ ## Personalización
427
+
428
+ ### Custom Styling
429
+
430
+ ```tsx
431
+ <FileUpload
432
+ selectedFile={file}
433
+ onFileSelect={setFile}
434
+ className="adm:max-w-2xl adm:mx-auto"
435
+ />
436
+ ```
437
+
438
+ ### Con Wrapper Adicional
439
+
440
+ ```tsx
441
+ <div className="adm:space-y-4">
442
+ <Label>Upload your document</Label>
443
+ <FileUpload
444
+ selectedFile={file}
445
+ onFileSelect={setFile}
446
+ />
447
+ {file && (
448
+ <Typography color="success">
449
+ ✓ File ready to upload
450
+ </Typography>
451
+ )}
452
+ </div>
453
+ ```
454
+
455
+ ## Comparación con Input type="file"
456
+
457
+ | Característica | Input file nativo | FileUpload |
458
+ |----------------|-------------------|------------|
459
+ | Drag & Drop | ❌ No | ✅ Sí |
460
+ | Validación visual | ❌ No | ✅ Sí |
461
+ | Preview del archivo | ❌ No | ✅ Sí con nombre y tamaño |
462
+ | Extensiones validadas | ⚠️ Solo accept attribute | ✅ Con feedback visual |
463
+ | Tamaño validado | ❌ Solo en backend | ✅ Cliente y servidor |
464
+ | UX consistente | ❌ Varía por browser | ✅ Consistente |
465
+ | Labels customizables | ❌ No | ✅ Sí (i18n friendly) |
466
+
467
+ ## Notas
468
+
469
+ - El componente usa `File` API nativa del browser
470
+ - La validación es solo en cliente - siempre validar también en el servidor
471
+ - El drag & drop solo funciona con un archivo a la vez
472
+ - Los archivos no se almacenan automáticamente - manejar el upload con `onFileSelect`
473
+ - El componente es completamente controlado - el padre maneja el estado del archivo
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adamosuiteservices/ui",
3
- "version": "2.13.0",
3
+ "version": "2.13.1",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",