@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.
Files changed (41) hide show
  1. package/dist/combobox-DGuQtXjP.js +608 -0
  2. package/dist/combobox-hTCtPMUL.cjs +40 -0
  3. package/dist/combobox.cjs +1 -1
  4. package/dist/combobox.js +1 -1
  5. package/dist/components/ui/combobox/combobox.d.ts +5 -1
  6. package/dist/components/ui/date-picker-selector/date-picker-selector.d.ts +5 -1
  7. package/dist/components/ui/file-upload/file-upload.d.ts +4 -1
  8. package/dist/components/ui/slider/slider.d.ts +1 -1
  9. package/dist/components/ui/switch/switch.d.ts +1 -1
  10. package/dist/date-picker-selector.cjs +1 -1
  11. package/dist/date-picker-selector.js +38 -28
  12. package/dist/field.cjs +2 -2
  13. package/dist/field.js +2 -2
  14. package/dist/file-upload.cjs +5 -3
  15. package/dist/file-upload.js +191 -150
  16. package/dist/slider.cjs +5 -5
  17. package/dist/slider.js +196 -177
  18. package/dist/styles.css +1 -1
  19. package/dist/switch.cjs +3 -3
  20. package/dist/switch.js +105 -85
  21. package/docs/components/ui/accordion-rounded.md +2 -6
  22. package/docs/components/ui/avatar.md +3 -1
  23. package/docs/components/ui/button.md +22 -16
  24. package/docs/components/ui/card.md +49 -31
  25. package/docs/components/ui/checkbox.md +44 -8
  26. package/docs/components/ui/combobox.md +62 -4
  27. package/docs/components/ui/date-picker-selector.md +90 -26
  28. package/docs/components/ui/file-upload.md +364 -95
  29. package/docs/components/ui/icon.md +1 -1
  30. package/docs/components/ui/input.md +4 -1
  31. package/docs/components/ui/radio-group.md +1 -1
  32. package/docs/components/ui/select.md +51 -34
  33. package/docs/components/ui/sheet.md +3 -9
  34. package/docs/components/ui/slider.md +120 -99
  35. package/docs/components/ui/switch.md +408 -408
  36. package/docs/components/ui/textarea.md +37 -22
  37. package/docs/components/ui/tooltip.md +5 -2
  38. package/docs/components/ui/typography.md +63 -39
  39. package/package.json +1 -1
  40. package/dist/combobox-CCJGIMFQ.cjs +0 -40
  41. 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 { FileUploadProps, FileUploadLabels } from "@adamosuiteservices/ui/file-upload";
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
- ```tsx
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
- Marca el componente como inválido, aplicando estilos destructivos. Por defecto: `false`
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
- ```tsx
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
- ### Archivos de Excel (Por Defecto)
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: "Supported formats: JPG, PNG, GIF, WebP. Max 5MB each. Up to 10 images.",
393
- filesSelected: (count) => `${count} image${count !== 1 ? "s" : ""} selected`
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
- ```tsx
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
- ## Validacióne={document}
464
- onFileSelect={setDocument}
465
- acceptedExtensions={[".pdf"]}
466
- maxSizeInMB={20}
467
- labels={{
468
- dragDrop: "Drag and drop your PDF here or",
469
- selectFile: "Select PDF",
470
- fileRequirements: "Only PDF files. Maximum 20 MB."
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
- ```typescript
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"> & Readonly<{
778
- selectedFile: File | null
779
- onFileSelect: (file: File | null) => void
780
- acceptedExtensions?: string[]
781
- maxSizeInMB?: number
782
- labels?: FileUploadLabels
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("metadata", JSON.stringify({
795
- uploadedAt: new Date().toISOString()
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
- ```tsx
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
- selectedFile={file}
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 | Input file nativo | FileUpload |
867
- |----------------|-------------------|------------|
868
- | Drag & Drop | ❌ No | ✅ Sí |
869
- | Validación visual | ❌ No | ✅ Sí |
870
- | Preview del archivo | ❌ No | ✅ Sí con nombre y tamaño |
871
- | Extensiones validadas | ⚠️ Solo accept attribute | ✅ Con feedback visual |
872
- | Tamaño validado | ❌ Solo en backend | ✅ Cliente y servidor |
873
- | UX consistente | ❌ Varía por browser | ✅ Consistente |
874
- | Labels customizables | ❌ No | ✅ Sí (i18n friendly) |
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