@adamosuiteservices/ui 2.11.15 → 2.11.16

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 (40) hide show
  1. package/dist/colors.css +1 -1
  2. package/dist/styles.css +1 -1
  3. package/docs/AI-GUIDE.md +321 -321
  4. package/docs/components/layout/sidebar.md +399 -399
  5. package/docs/components/layout/toaster.md +436 -436
  6. package/docs/components/ui/accordion-rounded.md +584 -584
  7. package/docs/components/ui/accordion.md +269 -269
  8. package/docs/components/ui/button-group.md +984 -984
  9. package/docs/components/ui/button.md +1137 -1137
  10. package/docs/components/ui/calendar.md +1159 -1159
  11. package/docs/components/ui/card.md +1455 -1455
  12. package/docs/components/ui/checkbox.md +292 -292
  13. package/docs/components/ui/collapsible.md +323 -323
  14. package/docs/components/ui/command.md +454 -454
  15. package/docs/components/ui/context-menu.md +540 -540
  16. package/docs/components/ui/dialog.md +628 -628
  17. package/docs/components/ui/dropdown-menu.md +709 -709
  18. package/docs/components/ui/field.md +706 -706
  19. package/docs/components/ui/hover-card.md +446 -446
  20. package/docs/components/ui/input.md +362 -362
  21. package/docs/components/ui/kbd.md +434 -434
  22. package/docs/components/ui/label.md +359 -359
  23. package/docs/components/ui/pagination.md +650 -650
  24. package/docs/components/ui/popover.md +536 -536
  25. package/docs/components/ui/progress.md +182 -182
  26. package/docs/components/ui/radio-group.md +311 -311
  27. package/docs/components/ui/select.md +352 -352
  28. package/docs/components/ui/separator.md +214 -214
  29. package/docs/components/ui/sheet.md +142 -142
  30. package/docs/components/ui/skeleton.md +140 -140
  31. package/docs/components/ui/slider.md +341 -341
  32. package/docs/components/ui/spinner.md +170 -170
  33. package/docs/components/ui/switch.md +408 -408
  34. package/docs/components/ui/tabs-underline.md +106 -106
  35. package/docs/components/ui/tabs.md +122 -122
  36. package/docs/components/ui/textarea.md +243 -243
  37. package/docs/components/ui/toggle.md +237 -237
  38. package/docs/components/ui/tooltip.md +317 -317
  39. package/docs/components/ui/typography.md +280 -280
  40. package/package.json +1 -1
@@ -1,1137 +1,1137 @@
1
- # Button Component
2
-
3
- ## Descripción
4
-
5
- Componente fundamental para **acciones e interacciones del usuario**. Ofrece 11 variantes visuales, 6 tamaños, estados automáticos (hover, focus, disabled, loading), iconos auto-dimensionados, y la capacidad de renderizarse como cualquier elemento usando `asChild`. Basado en CVA para variants y Radix UI Slot para composición.
6
-
7
- ## Características
8
-
9
- - ✅ 12 variantes: default, success (2), warning (2), destructive (2), secondary, outline, ghost, ghost-neutral, link
10
- - ✅ 6 tamaños: sm, default, lg, icon, icon-sm, icon-lg
11
- - ✅ Iconos auto-dimensionados (16px) con ajuste automático de padding
12
- - ✅ Estados automáticos: hover, focus-visible, disabled, loading
13
- - ✅ Prop `asChild` para renderizar como links u otros elementos
14
- - ✅ Focus ring accesible con detección de teclado
15
- - ✅ Soporte para aria-invalid con anillos de error
16
- - ✅ Transiciones suaves en todos los estados
17
- - ✅ Dark mode completo en todas las variantes
18
-
19
- ## Importación
20
-
21
- ```typescript
22
- import { Button } from "@adamosuiteservices/ui/button";
23
- ```
24
-
25
- ## Uso Básico
26
-
27
- ### Botón Simple
28
-
29
- ```tsx
30
- <Button>Click me</Button>
31
- ```
32
-
33
- ### Con Icono
34
-
35
- ```tsx
36
- import { Icon } from "@adamosuiteservices/ui/icon";
37
-
38
- <Button>
39
- <Icon symbol="add" />
40
- Add Item
41
- </Button>;
42
- ```
43
-
44
- **Nota**: El icono se auto-dimensiona a 16px y el padding se ajusta automáticamente con `has-[>svg]`.
45
-
46
- ### Como Link (asChild)
47
-
48
- ```tsx
49
- <Button asChild>
50
- <a href="/dashboard">Go to Dashboard</a>
51
- </Button>
52
- ```
53
-
54
- **Uso de asChild**: Mantiene el estilo de Button pero renderiza el hijo (`<a>`) en lugar de `<button>`.
55
-
56
- ## Variantes
57
-
58
- ### default (Primary)
59
-
60
- Botón principal con colores de marca para acciones primarias.
61
-
62
- ```tsx
63
- <Button variant="default">Default</Button>
64
- ```
65
-
66
- **Estilos**:
67
-
68
- - Fondo: `bg-primary`
69
- - Texto: `text-primary-foreground`
70
- - Hover: `bg-primary-700`
71
- - Uso: Acciones primarias, CTAs principales
72
-
73
- ### success
74
-
75
- Botón verde sólido para acciones exitosas o confirmaciones importantes.
76
-
77
- ```tsx
78
- <Button variant="success">Success</Button>
79
- ```
80
-
81
- **Estilos**:
82
-
83
- - Fondo: `bg-success` (verde)
84
- - Texto: `text-white`
85
- - Hover: `bg-success-700`
86
- - Focus ring: `ring-success/20`
87
- - Dark mode: `bg-success/60`
88
- - Uso: Guardar, aprobar, confirmar, completar
89
-
90
- ### success-medium
91
-
92
- Versión suave del botón success con fondo claro y texto verde.
93
-
94
- ```tsx
95
- <Button variant="success-medium">Success Medium</Button>
96
- ```
97
-
98
- **Estilos**:
99
-
100
- - Fondo: `bg-success-50` (verde muy claro)
101
- - Texto: `text-success` (verde)
102
- - Hover: `bg-success/20`
103
- - Uso: Acciones exitosas secundarias, estados de éxito menos enfáticos
104
-
105
- ### warning
106
-
107
- Botón naranja/amarillo sólido para acciones que requieren precaución.
108
-
109
- ```tsx
110
- <Button variant="warning">Warning</Button>
111
- ```
112
-
113
- **Estilos**:
114
-
115
- - Fondo: `bg-warning` (naranja/amarillo)
116
- - Texto: `text-white`
117
- - Hover: `bg-warning-700`
118
- - Focus ring: `ring-warning/20`
119
- - Dark mode: `bg-warning/60`
120
- - Uso: Acciones de advertencia, confirmaciones con riesgo moderado
121
-
122
- ### warning-medium
123
-
124
- Versión suave del botón warning con fondo claro.
125
-
126
- ```tsx
127
- <Button variant="warning-medium">Warning Medium</Button>
128
- ```
129
-
130
- **Estilos**:
131
-
132
- - Fondo: `bg-warning-50` (naranja/amarillo muy claro)
133
- - Texto: `text-warning`
134
- - Hover: `bg-warning/20`
135
- - Uso: Advertencias secundarias, alertas menos críticas
136
-
137
- ### destructive
138
-
139
- Botón rojo sólido para acciones destructivas o peligrosas.
140
-
141
- ```tsx
142
- <Button variant="destructive">Destructive</Button>
143
- ```
144
-
145
- **Estilos**:
146
-
147
- - Fondo: `bg-destructive` (rojo)
148
- - Texto: `text-white`
149
- - Hover: `bg-destructive-700`
150
- - Focus ring: `ring-destructive/20`
151
- - Dark mode: `bg-destructive/60`
152
- - Uso: Eliminar, cancelar suscripción, reset, acciones irreversibles
153
-
154
- ### destructive-medium
155
-
156
- Versión suave del botón destructive con fondo claro.
157
-
158
- ```tsx
159
- <Button variant="destructive-medium">Destructive Medium</Button>
160
- ```
161
-
162
- **Estilos**:
163
-
164
- - Fondo: `bg-destructive-50` (rojo muy claro)
165
- - Texto: `text-destructive`
166
- - Hover: `bg-destructive/20`
167
- - Uso: Acciones destructivas secundarias, advertencias de error
168
-
169
- ### secondary
170
-
171
- Botón secundario para acciones de menor importancia.
172
-
173
- ```tsx
174
- <Button variant="secondary">Secondary</Button>
175
- ```
176
-
177
- **Estilos**:
178
-
179
- - Fondo: `bg-secondary`
180
- - Texto: `text-secondary-foreground`
181
- - Hover: `bg-primary-200`
182
- - Uso: Acciones secundarias, botones de soporte
183
-
184
- ### outline
185
-
186
- Botón con borde y fondo transparente para acciones secundarias.
187
-
188
- ```tsx
189
- <Button variant="outline">Outline</Button>
190
- ```
191
-
192
- **Estilos**:
193
-
194
- - Fondo: `bg-background` con `border`
195
- - Shadow: `shadow-xs`
196
- - Hover: `bg-accent`
197
- - Dark mode: `bg-input/30`, `border-input`
198
- - Uso: Cancelar, acciones alternativas, botones en grupos
199
-
200
- ### ghost
201
-
202
- Botón mínimo sin borde con color primario que solo aparece en hover.
203
-
204
- ```tsx
205
- <Button variant="ghost">Ghost</Button>
206
- ```
207
-
208
- **Estilos**:
209
-
210
- - Fondo: Transparente
211
- - Texto: `text-primary`
212
- - Hover: `bg-primary-50`, `text-primary`
213
- - Dark mode: `hover:bg-primary-50/20`
214
- - Uso: Acciones terciarias con énfasis primario, iconos de herramientas, menús
215
-
216
- ### ghost-neutral
217
-
218
- Botón mínimo sin borde con color neutral que solo aparece en hover.
219
-
220
- ```tsx
221
- <Button variant="ghost-neutral">Ghost Neutral</Button>
222
- ```
223
-
224
- **Estilos**:
225
-
226
- - Fondo: Transparente
227
- - Texto: Color heredado del contexto
228
- - Hover: `bg-accent`, `text-accent-foreground`
229
- - Dark mode: `hover:bg-accent/50`
230
- - Uso: Acciones terciarias neutras, iconos de herramientas sin énfasis, controles de interfaz
231
-
232
- ### link
233
-
234
- Botón que se ve como un link con subrayado en hover.
235
-
236
- ```tsx
237
- <Button variant="link">Link</Button>
238
- ```
239
-
240
- **Estilos**:
241
-
242
- - Color: `text-primary`
243
- - Hover: `underline`
244
- - Offset: `underline-offset-4`
245
- - Uso: Navegación inline, acciones de bajo énfasis
246
-
247
- ## Tamaños
248
-
249
- ### sm (Small)
250
-
251
- Botón compacto para espacios reducidos.
252
-
253
- ```tsx
254
- <Button size="sm">Small</Button>
255
- ```
256
-
257
- **Dimensiones**:
258
-
259
- - Altura: `h-8` (32px)
260
- - Padding horizontal: `px-3` (12px)
261
- - Padding con icono: `px-2.5` (10px) con `has-[>svg]`
262
- - Gap: `gap-1.5` (6px)
263
- - Border radius: `rounded-md`
264
-
265
- ### default
266
-
267
- Tamaño estándar para la mayoría de casos.
268
-
269
- ```tsx
270
- <Button size="default">Default</Button>
271
- ```
272
-
273
- **Dimensiones**:
274
-
275
- - Altura: `h-9` (36px)
276
- - Padding horizontal: `px-4` (16px)
277
- - Padding con icono: `px-3` (12px) con `has-[>svg]`
278
- - Gap: `gap-2` (8px)
279
- - Border radius: `rounded-md`
280
-
281
- ### lg (Large)
282
-
283
- Botón grande para CTAs prominentes.
284
-
285
- ```tsx
286
- <Button size="lg">Large</Button>
287
- ```
288
-
289
- **Dimensiones**:
290
-
291
- - Altura: `h-10` (40px)
292
- - Padding horizontal: `px-6` (24px)
293
- - Padding con icono: `px-4` (16px) con `has-[>svg]`
294
- - Gap: `gap-2` (8px)
295
- - Border radius: `rounded-md`
296
-
297
- ### icon (Icon Only - Default)
298
-
299
- Botón cuadrado solo para iconos (tamaño medio).
300
-
301
- ```tsx
302
- <Button size="icon" aria-label="Settings">
303
- <SettingsIcon />
304
- </Button>
305
- ```
306
-
307
- **Dimensiones**:
308
-
309
- - Tamaño: `size-9` (36x36px)
310
- - **Importante**: Siempre incluye `aria-label` para accesibilidad
311
-
312
- ### icon-sm (Icon Small)
313
-
314
- Botón cuadrado pequeño para iconos.
315
-
316
- ```tsx
317
- <Button size="icon-sm" aria-label="Edit">
318
- <EditIcon />
319
- </Button>
320
- ```
321
-
322
- **Dimensiones**:
323
-
324
- - Tamaño: `size-8` (32x32px)
325
-
326
- ### icon-lg (Icon Large)
327
-
328
- Botón cuadrado grande para iconos.
329
-
330
- ```tsx
331
- <Button size="icon-lg" aria-label="Search">
332
- <SearchIcon />
333
- </Button>
334
- ```
335
-
336
- **Dimensiones**:
337
-
338
- - Tamaño: `size-10` (40x40px)
339
-
340
- ## Estados
341
-
342
- ### Normal (Default)
343
-
344
- Estado por defecto con interacciones completas.
345
-
346
- ```tsx
347
- <Button>Normal State</Button>
348
- ```
349
-
350
- ### Disabled
351
-
352
- Desactiva el botón y reduce opacidad.
353
-
354
- ```tsx
355
- <Button disabled>Disabled</Button>
356
- ```
357
-
358
- **Comportamiento**:
359
-
360
- - `opacity-50`: Opacidad al 50%
361
- - `pointer-events-none`: No acepta clicks ni hover
362
- - Automático en todos los navegadores
363
-
364
- ### Loading
365
-
366
- Muestra spinner y desactiva interacciones.
367
-
368
- ```tsx
369
- import { Icon } from "@adamosuiteservices/ui/icon";
370
-
371
- <Button disabled>
372
- <Icon symbol="progress_activity" className="animate-spin" />
373
- Loading...
374
- </Button>;
375
- ```
376
-
377
- **Patrón recomendado**:
378
-
379
- ```tsx
380
- const [isLoading, setIsLoading] = useState(false);
381
-
382
- <Button disabled={isLoading} onClick={handleSubmit}>
383
- {isLoading && <Icon symbol="progress_activity" className="animate-spin" />}
384
- {isLoading ? "Saving..." : "Save Changes"}
385
- </Button>;
386
- ```
387
-
388
- ### Hover
389
-
390
- Estado automático al pasar el mouse.
391
-
392
- - Cada variante tiene su propio color de hover
393
- - Transición suave con `transition-all`
394
- - No afecta a botones disabled
395
-
396
- ### Focus-Visible
397
-
398
- Anillo visible solo al navegar con teclado.
399
-
400
- ```tsx
401
- <Button>Focus me with Tab key</Button>
402
- ```
403
-
404
- **Estilos aplicados**:
405
-
406
- - `focus-visible:border-ring`: Borde del color del anillo
407
- - `focus-visible:ring-ring/50`: Anillo exterior con 50% opacidad
408
- - `focus-visible:ring-[3px]`: Anillo de 3px
409
- - Solo visible con teclado (no con mouse)
410
-
411
- ### Invalid (Error State)
412
-
413
- Para formularios con validación.
414
-
415
- ```tsx
416
- <Button aria-invalid="true">Invalid Button</Button>
417
- ```
418
-
419
- **Estilos aplicados**:
420
-
421
- - `aria-invalid:ring-destructive/20`: Anillo rojo claro
422
- - `aria-invalid:border-destructive`: Borde rojo
423
- - Dark mode: `ring-destructive/40`
424
-
425
- ## Patrones Avanzados
426
-
427
- ### Botón con Icono (Text + Icon)
428
-
429
- ```tsx
430
- import { Icon } from "@adamosuiteservices/ui/icon";
431
-
432
- {
433
- /* Icono a la izquierda */
434
- }
435
- <Button>
436
- <Icon symbol="save" />
437
- Save Changes
438
- </Button>;
439
-
440
- {
441
- /* Icono a la derecha */
442
- }
443
- <Button>
444
- Download
445
- <Icon symbol="download" />
446
- </Button>;
447
-
448
- {
449
- /* Múltiples iconos */
450
- }
451
- <Button variant="destructive">
452
- <Icon symbol="delete" />
453
- Delete All
454
- <span className="ml-auto text-xs">(5)</span>
455
- </Button>;
456
- ```
457
-
458
- **Nota**: El gap (`gap-2`) se aplica automáticamente entre elementos.
459
-
460
- ### Botón Redondeado (Pill)
461
-
462
- ```tsx
463
- <Button className="rounded-full">
464
- <PlusIcon />
465
- Add New
466
- </Button>
467
- ```
468
-
469
- ### Botón de Ancho Completo
470
-
471
- ```tsx
472
- <Button className="w-full">Continue</Button>
473
- ```
474
-
475
- ### Grupo de Botones
476
-
477
- ```tsx
478
- <div className="flex gap-2">
479
- <Button variant="outline">Cancel</Button>
480
- <Button>Save</Button>
481
- </div>
482
- ```
483
-
484
- ### Botón Interactivo con Estado
485
-
486
- ```tsx
487
- import { useState } from "react";
488
- import { Icon } from "@adamosuiteservices/ui/icon";
489
-
490
- function LikeButton() {
491
- const [liked, setLiked] = useState(false);
492
-
493
- return (
494
- <Button
495
- variant={liked ? "default" : "outline"}
496
- onClick={() => setLiked(!liked)}
497
- >
498
- <Icon symbol="favorite" className={liked ? "fill-current" : ""} />
499
- {liked ? "Liked" : "Like"}
500
- </Button>
501
- );
502
- }
503
-
504
- function SaveButton() {
505
- const [saved, setSaved] = useState(false);
506
-
507
- return (
508
- <Button
509
- variant={saved ? "secondary" : "outline"}
510
- onClick={() => setSaved(!saved)}
511
- >
512
- <Icon symbol="star" className={saved ? "fill-current" : ""} />
513
- {saved ? "Saved" : "Save"}
514
- </Button>
515
- );
516
- }
517
-
518
- function PlayButton() {
519
- const [playing, setPlaying] = useState(false);
520
-
521
- return (
522
- <Button variant="outline" onClick={() => setPlaying(!playing)}>
523
- {playing ? <Icon symbol="pause" /> : <Icon symbol="play_arrow" />}
524
- {playing ? "Pause" : "Play"}
525
- </Button>
526
- );
527
- }
528
- ```
529
-
530
- ### asChild con Link (Next.js)
531
-
532
- ```tsx
533
- import Link from "next/link";
534
- import { Icon } from "@adamosuiteservices/ui/icon";
535
-
536
- <Button asChild>
537
- <Link href="/dashboard">
538
- Go to Dashboard
539
- </Link>
540
- </Button>
541
-
542
- <Button asChild variant="outline">
543
- <a href="https://example.com" target="_blank" rel="noopener noreferrer">
544
- <Icon symbol="open_in_new" />
545
- External Link
546
- </a>
547
- </Button>
548
- ```
549
-
550
- ### asChild con React Router
551
-
552
- ```tsx
553
- import { Link } from "react-router-dom";
554
-
555
- <Button asChild>
556
- <Link to="/profile">View Profile</Link>
557
- </Button>;
558
- ```
559
-
560
- ### Botón con Badge
561
-
562
- ```tsx
563
- import { Badge } from "@adamosuiteservices/ui/badge";
564
-
565
- <Button variant="outline" className="relative">
566
- Notifications
567
- <Badge
568
- variant="destructive"
569
- className="absolute -top-2 -right-2 h-5 w-5 rounded-full p-0 flex items-center justify-center text-xs"
570
- >
571
- 5
572
- </Badge>
573
- </Button>;
574
- ```
575
-
576
- ### Botón Dropdown Trigger
577
-
578
- ```tsx
579
- import { Icon } from "@adamosuiteservices/ui/icon";
580
-
581
- <Button>
582
- Options
583
- <Icon symbol="keyboard_arrow_down" className="ml-auto -mr-1" />
584
- </Button>;
585
- ```
586
-
587
- ## Props
588
-
589
- | Prop | Tipo | Default | Descripción |
590
- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | -------------------------------------------------------------- |
591
- | variant | `"default" \| "success" \| "success-medium" \| "warning" \| "warning-medium" \| "destructive" \| "destructive-medium" \| "secondary" \| "outline" \| "ghost" \| "ghost-neutral" \| "link"` | `"default"` | Variante visual del botón |
592
- | size | `"default" \| "sm" \| "lg" \| "icon" \| "icon-sm" \| "icon-lg"` | `"default"` | Tamaño del botón |
593
- | asChild | `boolean` | `false` | Renderiza el hijo en lugar de un `<button>`. Usa Radix UI Slot |
594
- | disabled | `boolean` | `false` | Desactiva el botón (opacity 50%, pointer-events none) |
595
- | className | `string` | - | Clases CSS adicionales |
596
- | children | `ReactNode` | - | Contenido del botón (texto, iconos, etc.) |
597
- | type | `"button" \| "submit" \| "reset"` | `"button"` | Tipo de botón HTML |
598
- | onClick | `(e: MouseEvent) => void` | - | Handler de click |
599
- | ...props | `ButtonHTMLAttributes` | - | Todas las props nativas de `<button>` |
600
-
601
- **Nota sobre asChild**: Cuando es `true`, el componente usa `Slot` de Radix UI para transferir todas las props y estilos al hijo directo.
602
-
603
- ## Estilos Base Automáticos
604
-
605
- Todos los botones tienen estos estilos aplicados automáticamente:
606
-
607
- ```css
608
- inline-flex /* Display flex inline */
609
- items-center /* Alinea verticalmente al centro */
610
- justify-center /* Centra horizontalmente */
611
- gap-2 /* Espacio de 8px entre hijos (varía por tamaño) */
612
- whitespace-nowrap /* No rompe líneas */
613
- rounded-md /* Border radius medium */
614
- text-sm /* Tamaño de fuente 14px */
615
- font-semibold /* Peso de fuente 600 */
616
- transition-all /* Transiciones suaves */
617
- shrink-0 /* No se encoge en flex/grid */
618
- outline-none /* Sin outline por defecto */
619
- ```
620
-
621
- ### Iconos Auto-dimensionados
622
-
623
- ```css
624
- [&_svg]:pointer-events-none /* Iconos no capturan clicks */
625
- [&_svg:not([class*=size-])]:size-4 /* SVG sin clase size-* → 16x16px */
626
- [&_svg]:shrink-0 /* Iconos no se encogen */
627
- ```
628
-
629
- ### Padding Dinámico con Iconos
630
-
631
- ```css
632
- /* size="default" */
633
- h-9 px-4 /* Sin icono: 16px padding horizontal */
634
- has-[>svg]:px-3 /* Con icono: 12px padding horizontal */
635
-
636
- /* size="sm" */
637
- h-8 px-3
638
- has-[>svg]:px-2.5
639
-
640
- /* size="lg" */
641
- h-10 px-6
642
- has-[>svg]:px-4
643
- ```
644
-
645
- **Beneficio**: El padding se reduce automáticamente cuando hay iconos para mantener proporciones visuales.
646
-
647
- ## Casos de Uso Comunes
648
-
649
- ### Acciones Primarias
650
-
651
- Para la acción más importante en la pantalla.
652
-
653
- ```tsx
654
- <div className="flex gap-2">
655
- <Button>
656
- <SaveIcon />
657
- Save Changes
658
- </Button>
659
- <Button>
660
- <PlusIcon />
661
- Create New
662
- </Button>
663
- <Button>
664
- <DownloadIcon />
665
- Export Data
666
- </Button>
667
- </div>
668
- ```
669
-
670
- ### Acciones Secundarias
671
-
672
- Para acciones alternativas o de menor prioridad.
673
-
674
- ```tsx
675
- <div className="flex gap-2">
676
- <Button variant="outline">
677
- <EditIcon />
678
- Edit
679
- </Button>
680
- <Button variant="outline">
681
- <CopyIcon />
682
- Copy
683
- </Button>
684
- <Button variant="outline">
685
- <ShareIcon />
686
- Share
687
- </Button>
688
- </div>
689
- ```
690
-
691
- ### Acciones Destructivas/Peligrosas
692
-
693
- Para acciones que requieren confirmación del usuario.
694
-
695
- ```tsx
696
- <div className="flex gap-2">
697
- <Button variant="destructive">
698
- <TrashIcon />
699
- Delete Item
700
- </Button>
701
- <Button variant="destructive">Reset All Data</Button>
702
- </div>
703
- ```
704
-
705
- ### Formularios
706
-
707
- Combinación típica en formularios.
708
-
709
- ```tsx
710
- <form onSubmit={handleSubmit}>
711
- {/* Campos del formulario */}
712
-
713
- <div className="flex gap-2 justify-end mt-4">
714
- <Button type="button" variant="outline" onClick={onCancel}>
715
- Cancel
716
- </Button>
717
- <Button type="submit">
718
- {isSubmitting ? (
719
- <>
720
- <Loader2Icon className="animate-spin" />
721
- Saving...
722
- </>
723
- ) : (
724
- <>
725
- <SaveIcon />
726
- Save
727
- </>
728
- )}
729
- </Button>
730
- </div>
731
- </form>
732
- ```
733
-
734
- ### Dialogs y Modals
735
-
736
- Acciones en footer de dialogs.
737
-
738
- ```tsx
739
- <DialogFooter>
740
- <Button variant="outline" onClick={onClose}>
741
- Cancel
742
- </Button>
743
- <Button onClick={onConfirm}>Confirm</Button>
744
- </DialogFooter>
745
- ```
746
-
747
- ### Toolbars
748
-
749
- Botones de herramientas con iconos.
750
-
751
- ```tsx
752
- {/* Toolbar con ghost para acciones primarias */}
753
- <div className="flex gap-1 p-2 border rounded-lg">
754
- <Button size="icon-sm" variant="ghost" aria-label="Bold">
755
- <BoldIcon />
756
- </Button>
757
- <Button size="icon-sm" variant="ghost" aria-label="Italic">
758
- <ItalicIcon />
759
- </Button>
760
- <Button size="icon-sm" variant="ghost" aria-label="Underline">
761
- <UnderlineIcon />
762
- </Button>
763
- <div className="w-px bg-border mx-1" />
764
- <Button size="icon-sm" variant="ghost" aria-label="Align left">
765
- <AlignLeftIcon />
766
- </Button>
767
- <Button size="icon-sm" variant="ghost" aria-label="Align center">
768
- <AlignCenterIcon />
769
- </Button>
770
- </div>
771
-
772
- {/* Toolbar con ghost-neutral para apariencia más sutil */}
773
- <div className="flex gap-1 p-2 border rounded-lg">
774
- <Button size="icon-sm" variant="ghost-neutral" aria-label="Bold">
775
- <BoldIcon />
776
- </Button>
777
- <Button size="icon-sm" variant="ghost-neutral" aria-label="Italic">
778
- <ItalicIcon />
779
- </Button>
780
- <Button size="icon-sm" variant="ghost-neutral" aria-label="Underline">
781
- <UnderlineIcon />
782
- </Button>
783
- </div>
784
- ```
785
-
786
- ### Call to Action (CTA)
787
-
788
- Botón prominente en landing pages.
789
-
790
- ```tsx
791
- <Button size="lg" className="text-base px-8">
792
- Get Started Free
793
- <ArrowRightIcon />
794
- </Button>
795
- ```
796
-
797
- ### Navegación
798
-
799
- Botones como links de navegación.
800
-
801
- ```tsx
802
- import { useNavigate } from "react-router-dom";
803
-
804
- function Navigation() {
805
- const navigate = useNavigate();
806
-
807
- return (
808
- <div className="flex gap-2">
809
- <Button variant="ghost" onClick={() => navigate("/home")}>
810
- Home
811
- </Button>
812
- <Button variant="ghost" onClick={() => navigate("/about")}>
813
- About
814
- </Button>
815
- <Button variant="ghost" onClick={() => navigate("/contact")}>
816
- Contact
817
- </Button>
818
- </div>
819
- );
820
- }
821
- ```
822
-
823
- ### Botones de Paginación
824
-
825
- ```tsx
826
- <div className="flex items-center gap-2">
827
- <Button size="icon-sm" variant="outline" disabled={currentPage === 1}>
828
- <ChevronLeftIcon />
829
- </Button>
830
- <span className="text-sm">
831
- Page {currentPage} of {totalPages}
832
- </span>
833
- <Button
834
- size="icon-sm"
835
- variant="outline"
836
- disabled={currentPage === totalPages}
837
- >
838
- <ChevronRightIcon />
839
- </Button>
840
- </div>
841
- ```
842
-
843
- ## Mejores Prácticas
844
-
845
- ### Jerarquía Visual
846
-
847
- ```tsx
848
- {
849
- /* ✅ Correcto - Jerarquía clara */
850
- }
851
- <div className="flex gap-2">
852
- <Button variant="outline">Cancel</Button>
853
- <Button>Save</Button> {/* Acción primaria más prominente */}
854
- </div>;
855
-
856
- {
857
- /* ❌ Incorrecto - Todas las acciones con igual énfasis */
858
- }
859
- <div className="flex gap-2">
860
- <Button>Cancel</Button>
861
- <Button>Save</Button>
862
- </div>;
863
- ```
864
-
865
- ### Textos Descriptivos
866
-
867
- ```tsx
868
- {/* ✅ Correcto - Texto claro */}
869
- <Button>Save Changes</Button>
870
- <Button variant="destructive">Delete Account</Button>
871
-
872
- {/* ❌ Incorrecto - Texto genérico */}
873
- <Button>OK</Button>
874
- <Button>Submit</Button>
875
- ```
876
-
877
- ### aria-label en Botones de Icono
878
-
879
- ```tsx
880
- {
881
- /* ✅ Correcto - Accesible */
882
- }
883
- <Button size="icon" aria-label="Settings">
884
- <SettingsIcon />
885
- </Button>;
886
-
887
- {
888
- /* ❌ Incorrecto - No accesible */
889
- }
890
- <Button size="icon">
891
- <SettingsIcon />
892
- </Button>;
893
- ```
894
-
895
- ### Loading State
896
-
897
- ```tsx
898
- {
899
- /* ✅ Correcto - Estado de carga explícito */
900
- }
901
- <Button disabled={isLoading}>
902
- {isLoading && <Loader2Icon className="animate-spin" />}
903
- {isLoading ? "Saving..." : "Save"}
904
- </Button>;
905
-
906
- {
907
- /* ❌ Incorrecto - Solo disabled sin feedback visual */
908
- }
909
- <Button disabled={isLoading}>Save</Button>;
910
- ```
911
-
912
- ### Variantes Semánticas
913
-
914
- ```tsx
915
- {/* ✅ Correcto - Variantes apropiadas */}
916
- <Button variant="success">Approve</Button>
917
- <Button variant="destructive">Delete</Button>
918
- <Button variant="warning">Proceed with Caution</Button>
919
-
920
- {/* ❌ Incorrecto - Variantes genéricas para acciones críticas */}
921
- <Button>Delete All Data</Button> {/* Debería ser destructive */}
922
- ```
923
-
924
- ### Ghost vs Ghost-Neutral
925
-
926
- ```tsx
927
- {/* ✅ Correcto - Ghost para acciones con énfasis primario */}
928
- <Button variant="ghost">
929
- <Icon symbol="add" />
930
- Add Item
931
- </Button>
932
-
933
- {/* ✅ Correcto - Ghost-neutral para controles sin énfasis */}
934
- <Button variant="ghost-neutral" size="icon" aria-label="Settings">
935
- <Icon symbol="settings" />
936
- </Button>
937
-
938
- {/* ✅ Correcto - Ghost-neutral en toolbars y paneles */}
939
- <div className="flex gap-1">
940
- <Button variant="ghost-neutral" size="icon-sm" aria-label="Edit">
941
- <Icon symbol="edit" />
942
- </Button>
943
- <Button variant="ghost-neutral" size="icon-sm" aria-label="Delete">
944
- <Icon symbol="delete" />
945
- </Button>
946
- </div>
947
-
948
- {/* ⚠️ Considerar - Ghost puede ser muy prominente en algunos contextos */}
949
- <Button variant="ghost">Close</Button> {/* Mejor usar ghost-neutral */}
950
- ```
951
-
952
- ### Espaciado Consistente
953
-
954
- ```tsx
955
- {
956
- /* ✅ Correcto - Gap consistente */
957
- }
958
- <div className="flex gap-2">
959
- <Button variant="outline">Back</Button>
960
- <Button variant="outline">Skip</Button>
961
- <Button>Next</Button>
962
- </div>;
963
-
964
- {
965
- /* ✅ También correcto - Separar acciones por importancia */
966
- }
967
- <div className="flex justify-between">
968
- <Button variant="outline">Cancel</Button>
969
- <div className="flex gap-2">
970
- <Button variant="outline">Save as Draft</Button>
971
- <Button>Publish</Button>
972
- </div>
973
- </div>;
974
- ```
975
-
976
- ## Notas de Implementación
977
-
978
- - Basado en CVA (class-variance-authority) para variants system
979
- - Usa Radix UI Slot para la prop `asChild`
980
- - Los iconos SVG sin clase `size-*` se auto-dimensionan a 16px
981
- - Padding horizontal se ajusta automáticamente con `has-[>svg]` selector
982
- - Focus ring usa `focus-visible` (solo teclado, no mouse)
983
- - Transiciones en `all` para suavidad en todos los cambios
984
- - Sistema de slots con `data-slot="button"` para identificación
985
- - Todos los botones son `inline-flex` para alineación perfecta
986
- - `shrink-0` evita que botones se encojan en layouts flex
987
-
988
- ## Accesibilidad
989
-
990
- ### Navegación por Teclado
991
-
992
- - ✅ **Tab**: Navega al botón
993
- - ✅ **Space/Enter**: Activa el botón
994
- - ✅ **Escape**: Sale del focus (en algunos contextos)
995
-
996
- ### Estados Visuales Claros
997
-
998
- - ✅ Focus ring visible solo con teclado (`focus-visible`)
999
- - ✅ Disabled state con opacity reducida
1000
- - ✅ Hover state en todas las variantes
1001
- - ✅ Contraste de colores cumple WCAG AA
1002
-
1003
- ### ARIA Best Practices
1004
-
1005
- ```tsx
1006
- {/* ✅ Botón de icono con aria-label */}
1007
- <Button size="icon" aria-label="Delete item">
1008
- <TrashIcon />
1009
- </Button>
1010
-
1011
- {/* ✅ Botón con estado de carga */}
1012
- <Button disabled={isLoading} aria-busy={isLoading}>
1013
- {isLoading && <Loader2Icon className="animate-spin" />}
1014
- {isLoading ? "Loading..." : "Submit"}
1015
- </Button>
1016
-
1017
- {/* ✅ Botón con estado pressed (toggle) */}
1018
- <Button
1019
- variant={isActive ? "default" : "outline"}
1020
- aria-pressed={isActive}
1021
- onClick={() => setIsActive(!isActive)}
1022
- >
1023
- {isActive ? "Active" : "Inactive"}
1024
- </Button>
1025
-
1026
- {/* ✅ Botón con descripción expandida */}
1027
- <Button aria-describedby="delete-description">
1028
- Delete Account
1029
- </Button>
1030
- <p id="delete-description" className="sr-only">
1031
- This action cannot be undone. All your data will be permanently deleted.
1032
- </p>
1033
- ```
1034
-
1035
- ### Type en Formularios
1036
-
1037
- ```tsx
1038
- {
1039
- /* ✅ Correcto - type explícito */
1040
- }
1041
- <form onSubmit={handleSubmit}>
1042
- <Button type="submit">Submit</Button>
1043
- <Button type="button" onClick={handleCancel}>
1044
- Cancel
1045
- </Button>
1046
- <Button type="reset">Reset Form</Button>
1047
- </form>;
1048
-
1049
- {
1050
- /* ⚠️ Cuidado - sin type en form, default es submit */
1051
- }
1052
- <form>
1053
- <Button>Click me</Button> {/* Se comporta como submit */}
1054
- </form>;
1055
- ```
1056
-
1057
- ### Screen Readers
1058
-
1059
- - ✅ Texto descriptivo en lugar de solo iconos
1060
- - ✅ `aria-label` para botones de solo icono
1061
- - ✅ `aria-busy` durante estados de carga
1062
- - ✅ `aria-invalid` para estados de error
1063
- - ✅ Disabled buttons se anuncian como "disabled" o "dimmed"
1064
-
1065
- ## Troubleshooting
1066
-
1067
- ### Botón No Se Ve Como Esperado
1068
-
1069
- **Problema**: Los estilos no se aplican correctamente.
1070
-
1071
- **Solución**:
1072
-
1073
- ```tsx
1074
- // ❌ Incorrecto - className sobrescribe variantes
1075
- <Button className="bg-destructive">Click</Button>
1076
-
1077
- // ✅ Correcto - Usa variantes predefinidas
1078
- <Button variant="destructive">Click</Button>
1079
-
1080
- // ✅ O combina correctamente
1081
- <Button variant="destructive" className="rounded-full">Click</Button>
1082
- ```
1083
-
1084
- ### Icono No Se Auto-Dimensiona
1085
-
1086
- **Problema**: El icono es muy grande o muy pequeño.
1087
-
1088
- **Solución**:
1089
-
1090
- ```tsx
1091
- // ❌ Incorrecto - clase size-* evita auto-dimensionado
1092
- <Button>
1093
- <SaveIcon className="size-8" /> {/* Muy grande */}
1094
- Save
1095
- </Button>
1096
-
1097
- // ✅ Correcto - sin clase size-*
1098
- <Button>
1099
- <SaveIcon /> {/* Auto-dimensionado a 16px */}
1100
- Save
1101
- </Button>
1102
-
1103
- // ✅ O especifica tamaño personalizado si es necesario
1104
- <Button>
1105
- <SaveIcon className="h-5 w-5" />
1106
- Save
1107
- </Button>
1108
- ```
1109
-
1110
- ### asChild No Funciona
1111
-
1112
- **Problema**: El hijo no recibe los estilos.
1113
-
1114
- **Solución**:
1115
-
1116
- ```tsx
1117
- // ❌ Incorrecto - múltiples hijos
1118
- <Button asChild>
1119
- <a href="/link">Click</a>
1120
- <span>Extra</span>
1121
- </Button>
1122
-
1123
- // ✅ Correcto - un solo hijo directo
1124
- <Button asChild>
1125
- <a href="/link">
1126
- <LinkIcon />
1127
- Click Me
1128
- </a>
1129
- </Button>
1130
- ```
1131
-
1132
- ## Referencias
1133
-
1134
- - CVA (Class Variance Authority): https://cva.style/docs
1135
- - Radix UI Slot: https://www.radix-ui.com/primitives/docs/utilities/slot
1136
- - shadcn/ui Button: https://ui.shadcn.com/docs/components/button
1137
- - WCAG 2.1 Button Guidelines: https://www.w3.org/WAI/ARIA/apg/patterns/button/
1
+ # Button Component
2
+
3
+ ## Descripción
4
+
5
+ Componente fundamental para **acciones e interacciones del usuario**. Ofrece 11 variantes visuales, 6 tamaños, estados automáticos (hover, focus, disabled, loading), iconos auto-dimensionados, y la capacidad de renderizarse como cualquier elemento usando `asChild`. Basado en CVA para variants y Radix UI Slot para composición.
6
+
7
+ ## Características
8
+
9
+ - ✅ 12 variantes: default, success (2), warning (2), destructive (2), secondary, outline, ghost, ghost-neutral, link
10
+ - ✅ 6 tamaños: sm, default, lg, icon, icon-sm, icon-lg
11
+ - ✅ Iconos auto-dimensionados (16px) con ajuste automático de padding
12
+ - ✅ Estados automáticos: hover, focus-visible, disabled, loading
13
+ - ✅ Prop `asChild` para renderizar como links u otros elementos
14
+ - ✅ Focus ring accesible con detección de teclado
15
+ - ✅ Soporte para aria-invalid con anillos de error
16
+ - ✅ Transiciones suaves en todos los estados
17
+ - ✅ Dark mode completo en todas las variantes
18
+
19
+ ## Importación
20
+
21
+ ```typescript
22
+ import { Button } from "@adamosuiteservices/ui/button";
23
+ ```
24
+
25
+ ## Uso Básico
26
+
27
+ ### Botón Simple
28
+
29
+ ```tsx
30
+ <Button>Click me</Button>
31
+ ```
32
+
33
+ ### Con Icono
34
+
35
+ ```tsx
36
+ import { Icon } from "@adamosuiteservices/ui/icon";
37
+
38
+ <Button>
39
+ <Icon symbol="add" />
40
+ Add Item
41
+ </Button>;
42
+ ```
43
+
44
+ **Nota**: El icono se auto-dimensiona a 16px y el padding se ajusta automáticamente con `has-[>svg]`.
45
+
46
+ ### Como Link (asChild)
47
+
48
+ ```tsx
49
+ <Button asChild>
50
+ <a href="/dashboard">Go to Dashboard</a>
51
+ </Button>
52
+ ```
53
+
54
+ **Uso de asChild**: Mantiene el estilo de Button pero renderiza el hijo (`<a>`) en lugar de `<button>`.
55
+
56
+ ## Variantes
57
+
58
+ ### default (Primary)
59
+
60
+ Botón principal con colores de marca para acciones primarias.
61
+
62
+ ```tsx
63
+ <Button variant="default">Default</Button>
64
+ ```
65
+
66
+ **Estilos**:
67
+
68
+ - Fondo: `bg-primary`
69
+ - Texto: `text-primary-foreground`
70
+ - Hover: `bg-primary-700`
71
+ - Uso: Acciones primarias, CTAs principales
72
+
73
+ ### success
74
+
75
+ Botón verde sólido para acciones exitosas o confirmaciones importantes.
76
+
77
+ ```tsx
78
+ <Button variant="success">Success</Button>
79
+ ```
80
+
81
+ **Estilos**:
82
+
83
+ - Fondo: `bg-success` (verde)
84
+ - Texto: `text-white`
85
+ - Hover: `bg-success-700`
86
+ - Focus ring: `ring-success/20`
87
+ - Dark mode: `bg-success/60`
88
+ - Uso: Guardar, aprobar, confirmar, completar
89
+
90
+ ### success-medium
91
+
92
+ Versión suave del botón success con fondo claro y texto verde.
93
+
94
+ ```tsx
95
+ <Button variant="success-medium">Success Medium</Button>
96
+ ```
97
+
98
+ **Estilos**:
99
+
100
+ - Fondo: `bg-success-50` (verde muy claro)
101
+ - Texto: `text-success` (verde)
102
+ - Hover: `bg-success/20`
103
+ - Uso: Acciones exitosas secundarias, estados de éxito menos enfáticos
104
+
105
+ ### warning
106
+
107
+ Botón naranja/amarillo sólido para acciones que requieren precaución.
108
+
109
+ ```tsx
110
+ <Button variant="warning">Warning</Button>
111
+ ```
112
+
113
+ **Estilos**:
114
+
115
+ - Fondo: `bg-warning` (naranja/amarillo)
116
+ - Texto: `text-white`
117
+ - Hover: `bg-warning-700`
118
+ - Focus ring: `ring-warning/20`
119
+ - Dark mode: `bg-warning/60`
120
+ - Uso: Acciones de advertencia, confirmaciones con riesgo moderado
121
+
122
+ ### warning-medium
123
+
124
+ Versión suave del botón warning con fondo claro.
125
+
126
+ ```tsx
127
+ <Button variant="warning-medium">Warning Medium</Button>
128
+ ```
129
+
130
+ **Estilos**:
131
+
132
+ - Fondo: `bg-warning-50` (naranja/amarillo muy claro)
133
+ - Texto: `text-warning`
134
+ - Hover: `bg-warning/20`
135
+ - Uso: Advertencias secundarias, alertas menos críticas
136
+
137
+ ### destructive
138
+
139
+ Botón rojo sólido para acciones destructivas o peligrosas.
140
+
141
+ ```tsx
142
+ <Button variant="destructive">Destructive</Button>
143
+ ```
144
+
145
+ **Estilos**:
146
+
147
+ - Fondo: `bg-destructive` (rojo)
148
+ - Texto: `text-white`
149
+ - Hover: `bg-destructive-700`
150
+ - Focus ring: `ring-destructive/20`
151
+ - Dark mode: `bg-destructive/60`
152
+ - Uso: Eliminar, cancelar suscripción, reset, acciones irreversibles
153
+
154
+ ### destructive-medium
155
+
156
+ Versión suave del botón destructive con fondo claro.
157
+
158
+ ```tsx
159
+ <Button variant="destructive-medium">Destructive Medium</Button>
160
+ ```
161
+
162
+ **Estilos**:
163
+
164
+ - Fondo: `bg-destructive-50` (rojo muy claro)
165
+ - Texto: `text-destructive`
166
+ - Hover: `bg-destructive/20`
167
+ - Uso: Acciones destructivas secundarias, advertencias de error
168
+
169
+ ### secondary
170
+
171
+ Botón secundario para acciones de menor importancia.
172
+
173
+ ```tsx
174
+ <Button variant="secondary">Secondary</Button>
175
+ ```
176
+
177
+ **Estilos**:
178
+
179
+ - Fondo: `bg-secondary`
180
+ - Texto: `text-secondary-foreground`
181
+ - Hover: `bg-primary-200`
182
+ - Uso: Acciones secundarias, botones de soporte
183
+
184
+ ### outline
185
+
186
+ Botón con borde y fondo transparente para acciones secundarias.
187
+
188
+ ```tsx
189
+ <Button variant="outline">Outline</Button>
190
+ ```
191
+
192
+ **Estilos**:
193
+
194
+ - Fondo: `bg-background` con `border`
195
+ - Shadow: `shadow-xs`
196
+ - Hover: `bg-accent`
197
+ - Dark mode: `bg-input/30`, `border-input`
198
+ - Uso: Cancelar, acciones alternativas, botones en grupos
199
+
200
+ ### ghost
201
+
202
+ Botón mínimo sin borde con color primario que solo aparece en hover.
203
+
204
+ ```tsx
205
+ <Button variant="ghost">Ghost</Button>
206
+ ```
207
+
208
+ **Estilos**:
209
+
210
+ - Fondo: Transparente
211
+ - Texto: `text-primary`
212
+ - Hover: `bg-primary-50`, `text-primary`
213
+ - Dark mode: `hover:bg-primary-50/20`
214
+ - Uso: Acciones terciarias con énfasis primario, iconos de herramientas, menús
215
+
216
+ ### ghost-neutral
217
+
218
+ Botón mínimo sin borde con color neutral que solo aparece en hover.
219
+
220
+ ```tsx
221
+ <Button variant="ghost-neutral">Ghost Neutral</Button>
222
+ ```
223
+
224
+ **Estilos**:
225
+
226
+ - Fondo: Transparente
227
+ - Texto: Color heredado del contexto
228
+ - Hover: `bg-accent`, `text-accent-foreground`
229
+ - Dark mode: `hover:bg-accent/50`
230
+ - Uso: Acciones terciarias neutras, iconos de herramientas sin énfasis, controles de interfaz
231
+
232
+ ### link
233
+
234
+ Botón que se ve como un link con subrayado en hover.
235
+
236
+ ```tsx
237
+ <Button variant="link">Link</Button>
238
+ ```
239
+
240
+ **Estilos**:
241
+
242
+ - Color: `text-primary`
243
+ - Hover: `underline`
244
+ - Offset: `underline-offset-4`
245
+ - Uso: Navegación inline, acciones de bajo énfasis
246
+
247
+ ## Tamaños
248
+
249
+ ### sm (Small)
250
+
251
+ Botón compacto para espacios reducidos.
252
+
253
+ ```tsx
254
+ <Button size="sm">Small</Button>
255
+ ```
256
+
257
+ **Dimensiones**:
258
+
259
+ - Altura: `h-8` (32px)
260
+ - Padding horizontal: `px-3` (12px)
261
+ - Padding con icono: `px-2.5` (10px) con `has-[>svg]`
262
+ - Gap: `gap-1.5` (6px)
263
+ - Border radius: `rounded-md`
264
+
265
+ ### default
266
+
267
+ Tamaño estándar para la mayoría de casos.
268
+
269
+ ```tsx
270
+ <Button size="default">Default</Button>
271
+ ```
272
+
273
+ **Dimensiones**:
274
+
275
+ - Altura: `h-9` (36px)
276
+ - Padding horizontal: `px-4` (16px)
277
+ - Padding con icono: `px-3` (12px) con `has-[>svg]`
278
+ - Gap: `gap-2` (8px)
279
+ - Border radius: `rounded-md`
280
+
281
+ ### lg (Large)
282
+
283
+ Botón grande para CTAs prominentes.
284
+
285
+ ```tsx
286
+ <Button size="lg">Large</Button>
287
+ ```
288
+
289
+ **Dimensiones**:
290
+
291
+ - Altura: `h-10` (40px)
292
+ - Padding horizontal: `px-6` (24px)
293
+ - Padding con icono: `px-4` (16px) con `has-[>svg]`
294
+ - Gap: `gap-2` (8px)
295
+ - Border radius: `rounded-md`
296
+
297
+ ### icon (Icon Only - Default)
298
+
299
+ Botón cuadrado solo para iconos (tamaño medio).
300
+
301
+ ```tsx
302
+ <Button size="icon" aria-label="Settings">
303
+ <SettingsIcon />
304
+ </Button>
305
+ ```
306
+
307
+ **Dimensiones**:
308
+
309
+ - Tamaño: `size-9` (36x36px)
310
+ - **Importante**: Siempre incluye `aria-label` para accesibilidad
311
+
312
+ ### icon-sm (Icon Small)
313
+
314
+ Botón cuadrado pequeño para iconos.
315
+
316
+ ```tsx
317
+ <Button size="icon-sm" aria-label="Edit">
318
+ <EditIcon />
319
+ </Button>
320
+ ```
321
+
322
+ **Dimensiones**:
323
+
324
+ - Tamaño: `size-8` (32x32px)
325
+
326
+ ### icon-lg (Icon Large)
327
+
328
+ Botón cuadrado grande para iconos.
329
+
330
+ ```tsx
331
+ <Button size="icon-lg" aria-label="Search">
332
+ <SearchIcon />
333
+ </Button>
334
+ ```
335
+
336
+ **Dimensiones**:
337
+
338
+ - Tamaño: `size-10` (40x40px)
339
+
340
+ ## Estados
341
+
342
+ ### Normal (Default)
343
+
344
+ Estado por defecto con interacciones completas.
345
+
346
+ ```tsx
347
+ <Button>Normal State</Button>
348
+ ```
349
+
350
+ ### Disabled
351
+
352
+ Desactiva el botón y reduce opacidad.
353
+
354
+ ```tsx
355
+ <Button disabled>Disabled</Button>
356
+ ```
357
+
358
+ **Comportamiento**:
359
+
360
+ - `opacity-50`: Opacidad al 50%
361
+ - `pointer-events-none`: No acepta clicks ni hover
362
+ - Automático en todos los navegadores
363
+
364
+ ### Loading
365
+
366
+ Muestra spinner y desactiva interacciones.
367
+
368
+ ```tsx
369
+ import { Icon } from "@adamosuiteservices/ui/icon";
370
+
371
+ <Button disabled>
372
+ <Icon symbol="progress_activity" className="animate-spin" />
373
+ Loading...
374
+ </Button>;
375
+ ```
376
+
377
+ **Patrón recomendado**:
378
+
379
+ ```tsx
380
+ const [isLoading, setIsLoading] = useState(false);
381
+
382
+ <Button disabled={isLoading} onClick={handleSubmit}>
383
+ {isLoading && <Icon symbol="progress_activity" className="animate-spin" />}
384
+ {isLoading ? "Saving..." : "Save Changes"}
385
+ </Button>;
386
+ ```
387
+
388
+ ### Hover
389
+
390
+ Estado automático al pasar el mouse.
391
+
392
+ - Cada variante tiene su propio color de hover
393
+ - Transición suave con `transition-all`
394
+ - No afecta a botones disabled
395
+
396
+ ### Focus-Visible
397
+
398
+ Anillo visible solo al navegar con teclado.
399
+
400
+ ```tsx
401
+ <Button>Focus me with Tab key</Button>
402
+ ```
403
+
404
+ **Estilos aplicados**:
405
+
406
+ - `focus-visible:border-ring`: Borde del color del anillo
407
+ - `focus-visible:ring-ring/50`: Anillo exterior con 50% opacidad
408
+ - `focus-visible:ring-[3px]`: Anillo de 3px
409
+ - Solo visible con teclado (no con mouse)
410
+
411
+ ### Invalid (Error State)
412
+
413
+ Para formularios con validación.
414
+
415
+ ```tsx
416
+ <Button aria-invalid="true">Invalid Button</Button>
417
+ ```
418
+
419
+ **Estilos aplicados**:
420
+
421
+ - `aria-invalid:ring-destructive/20`: Anillo rojo claro
422
+ - `aria-invalid:border-destructive`: Borde rojo
423
+ - Dark mode: `ring-destructive/40`
424
+
425
+ ## Patrones Avanzados
426
+
427
+ ### Botón con Icono (Text + Icon)
428
+
429
+ ```tsx
430
+ import { Icon } from "@adamosuiteservices/ui/icon";
431
+
432
+ {
433
+ /* Icono a la izquierda */
434
+ }
435
+ <Button>
436
+ <Icon symbol="save" />
437
+ Save Changes
438
+ </Button>;
439
+
440
+ {
441
+ /* Icono a la derecha */
442
+ }
443
+ <Button>
444
+ Download
445
+ <Icon symbol="download" />
446
+ </Button>;
447
+
448
+ {
449
+ /* Múltiples iconos */
450
+ }
451
+ <Button variant="destructive">
452
+ <Icon symbol="delete" />
453
+ Delete All
454
+ <span className="ml-auto text-xs">(5)</span>
455
+ </Button>;
456
+ ```
457
+
458
+ **Nota**: El gap (`gap-2`) se aplica automáticamente entre elementos.
459
+
460
+ ### Botón Redondeado (Pill)
461
+
462
+ ```tsx
463
+ <Button className="rounded-full">
464
+ <PlusIcon />
465
+ Add New
466
+ </Button>
467
+ ```
468
+
469
+ ### Botón de Ancho Completo
470
+
471
+ ```tsx
472
+ <Button className="w-full">Continue</Button>
473
+ ```
474
+
475
+ ### Grupo de Botones
476
+
477
+ ```tsx
478
+ <div className="flex gap-2">
479
+ <Button variant="outline">Cancel</Button>
480
+ <Button>Save</Button>
481
+ </div>
482
+ ```
483
+
484
+ ### Botón Interactivo con Estado
485
+
486
+ ```tsx
487
+ import { useState } from "react";
488
+ import { Icon } from "@adamosuiteservices/ui/icon";
489
+
490
+ function LikeButton() {
491
+ const [liked, setLiked] = useState(false);
492
+
493
+ return (
494
+ <Button
495
+ variant={liked ? "default" : "outline"}
496
+ onClick={() => setLiked(!liked)}
497
+ >
498
+ <Icon symbol="favorite" className={liked ? "fill-current" : ""} />
499
+ {liked ? "Liked" : "Like"}
500
+ </Button>
501
+ );
502
+ }
503
+
504
+ function SaveButton() {
505
+ const [saved, setSaved] = useState(false);
506
+
507
+ return (
508
+ <Button
509
+ variant={saved ? "secondary" : "outline"}
510
+ onClick={() => setSaved(!saved)}
511
+ >
512
+ <Icon symbol="star" className={saved ? "fill-current" : ""} />
513
+ {saved ? "Saved" : "Save"}
514
+ </Button>
515
+ );
516
+ }
517
+
518
+ function PlayButton() {
519
+ const [playing, setPlaying] = useState(false);
520
+
521
+ return (
522
+ <Button variant="outline" onClick={() => setPlaying(!playing)}>
523
+ {playing ? <Icon symbol="pause" /> : <Icon symbol="play_arrow" />}
524
+ {playing ? "Pause" : "Play"}
525
+ </Button>
526
+ );
527
+ }
528
+ ```
529
+
530
+ ### asChild con Link (Next.js)
531
+
532
+ ```tsx
533
+ import Link from "next/link";
534
+ import { Icon } from "@adamosuiteservices/ui/icon";
535
+
536
+ <Button asChild>
537
+ <Link href="/dashboard">
538
+ Go to Dashboard
539
+ </Link>
540
+ </Button>
541
+
542
+ <Button asChild variant="outline">
543
+ <a href="https://example.com" target="_blank" rel="noopener noreferrer">
544
+ <Icon symbol="open_in_new" />
545
+ External Link
546
+ </a>
547
+ </Button>
548
+ ```
549
+
550
+ ### asChild con React Router
551
+
552
+ ```tsx
553
+ import { Link } from "react-router-dom";
554
+
555
+ <Button asChild>
556
+ <Link to="/profile">View Profile</Link>
557
+ </Button>;
558
+ ```
559
+
560
+ ### Botón con Badge
561
+
562
+ ```tsx
563
+ import { Badge } from "@adamosuiteservices/ui/badge";
564
+
565
+ <Button variant="outline" className="relative">
566
+ Notifications
567
+ <Badge
568
+ variant="destructive"
569
+ className="absolute -top-2 -right-2 h-5 w-5 rounded-full p-0 flex items-center justify-center text-xs"
570
+ >
571
+ 5
572
+ </Badge>
573
+ </Button>;
574
+ ```
575
+
576
+ ### Botón Dropdown Trigger
577
+
578
+ ```tsx
579
+ import { Icon } from "@adamosuiteservices/ui/icon";
580
+
581
+ <Button>
582
+ Options
583
+ <Icon symbol="keyboard_arrow_down" className="ml-auto -mr-1" />
584
+ </Button>;
585
+ ```
586
+
587
+ ## Props
588
+
589
+ | Prop | Tipo | Default | Descripción |
590
+ | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | -------------------------------------------------------------- |
591
+ | variant | `"default" \| "success" \| "success-medium" \| "warning" \| "warning-medium" \| "destructive" \| "destructive-medium" \| "secondary" \| "outline" \| "ghost" \| "ghost-neutral" \| "link"` | `"default"` | Variante visual del botón |
592
+ | size | `"default" \| "sm" \| "lg" \| "icon" \| "icon-sm" \| "icon-lg"` | `"default"` | Tamaño del botón |
593
+ | asChild | `boolean` | `false` | Renderiza el hijo en lugar de un `<button>`. Usa Radix UI Slot |
594
+ | disabled | `boolean` | `false` | Desactiva el botón (opacity 50%, pointer-events none) |
595
+ | className | `string` | - | Clases CSS adicionales |
596
+ | children | `ReactNode` | - | Contenido del botón (texto, iconos, etc.) |
597
+ | type | `"button" \| "submit" \| "reset"` | `"button"` | Tipo de botón HTML |
598
+ | onClick | `(e: MouseEvent) => void` | - | Handler de click |
599
+ | ...props | `ButtonHTMLAttributes` | - | Todas las props nativas de `<button>` |
600
+
601
+ **Nota sobre asChild**: Cuando es `true`, el componente usa `Slot` de Radix UI para transferir todas las props y estilos al hijo directo.
602
+
603
+ ## Estilos Base Automáticos
604
+
605
+ Todos los botones tienen estos estilos aplicados automáticamente:
606
+
607
+ ```css
608
+ inline-flex /* Display flex inline */
609
+ items-center /* Alinea verticalmente al centro */
610
+ justify-center /* Centra horizontalmente */
611
+ gap-2 /* Espacio de 8px entre hijos (varía por tamaño) */
612
+ whitespace-nowrap /* No rompe líneas */
613
+ rounded-md /* Border radius medium */
614
+ text-sm /* Tamaño de fuente 14px */
615
+ font-semibold /* Peso de fuente 600 */
616
+ transition-all /* Transiciones suaves */
617
+ shrink-0 /* No se encoge en flex/grid */
618
+ outline-none /* Sin outline por defecto */
619
+ ```
620
+
621
+ ### Iconos Auto-dimensionados
622
+
623
+ ```css
624
+ [&_svg]:pointer-events-none /* Iconos no capturan clicks */
625
+ [&_svg:not([class*=size-])]:size-4 /* SVG sin clase size-* → 16x16px */
626
+ [&_svg]:shrink-0 /* Iconos no se encogen */
627
+ ```
628
+
629
+ ### Padding Dinámico con Iconos
630
+
631
+ ```css
632
+ /* size="default" */
633
+ h-9 px-4 /* Sin icono: 16px padding horizontal */
634
+ has-[>svg]:px-3 /* Con icono: 12px padding horizontal */
635
+
636
+ /* size="sm" */
637
+ h-8 px-3
638
+ has-[>svg]:px-2.5
639
+
640
+ /* size="lg" */
641
+ h-10 px-6
642
+ has-[>svg]:px-4
643
+ ```
644
+
645
+ **Beneficio**: El padding se reduce automáticamente cuando hay iconos para mantener proporciones visuales.
646
+
647
+ ## Casos de Uso Comunes
648
+
649
+ ### Acciones Primarias
650
+
651
+ Para la acción más importante en la pantalla.
652
+
653
+ ```tsx
654
+ <div className="flex gap-2">
655
+ <Button>
656
+ <SaveIcon />
657
+ Save Changes
658
+ </Button>
659
+ <Button>
660
+ <PlusIcon />
661
+ Create New
662
+ </Button>
663
+ <Button>
664
+ <DownloadIcon />
665
+ Export Data
666
+ </Button>
667
+ </div>
668
+ ```
669
+
670
+ ### Acciones Secundarias
671
+
672
+ Para acciones alternativas o de menor prioridad.
673
+
674
+ ```tsx
675
+ <div className="flex gap-2">
676
+ <Button variant="outline">
677
+ <EditIcon />
678
+ Edit
679
+ </Button>
680
+ <Button variant="outline">
681
+ <CopyIcon />
682
+ Copy
683
+ </Button>
684
+ <Button variant="outline">
685
+ <ShareIcon />
686
+ Share
687
+ </Button>
688
+ </div>
689
+ ```
690
+
691
+ ### Acciones Destructivas/Peligrosas
692
+
693
+ Para acciones que requieren confirmación del usuario.
694
+
695
+ ```tsx
696
+ <div className="flex gap-2">
697
+ <Button variant="destructive">
698
+ <TrashIcon />
699
+ Delete Item
700
+ </Button>
701
+ <Button variant="destructive">Reset All Data</Button>
702
+ </div>
703
+ ```
704
+
705
+ ### Formularios
706
+
707
+ Combinación típica en formularios.
708
+
709
+ ```tsx
710
+ <form onSubmit={handleSubmit}>
711
+ {/* Campos del formulario */}
712
+
713
+ <div className="flex gap-2 justify-end mt-4">
714
+ <Button type="button" variant="outline" onClick={onCancel}>
715
+ Cancel
716
+ </Button>
717
+ <Button type="submit">
718
+ {isSubmitting ? (
719
+ <>
720
+ <Loader2Icon className="animate-spin" />
721
+ Saving...
722
+ </>
723
+ ) : (
724
+ <>
725
+ <SaveIcon />
726
+ Save
727
+ </>
728
+ )}
729
+ </Button>
730
+ </div>
731
+ </form>
732
+ ```
733
+
734
+ ### Dialogs y Modals
735
+
736
+ Acciones en footer de dialogs.
737
+
738
+ ```tsx
739
+ <DialogFooter>
740
+ <Button variant="outline" onClick={onClose}>
741
+ Cancel
742
+ </Button>
743
+ <Button onClick={onConfirm}>Confirm</Button>
744
+ </DialogFooter>
745
+ ```
746
+
747
+ ### Toolbars
748
+
749
+ Botones de herramientas con iconos.
750
+
751
+ ```tsx
752
+ {/* Toolbar con ghost para acciones primarias */}
753
+ <div className="flex gap-1 p-2 border rounded-lg">
754
+ <Button size="icon-sm" variant="ghost" aria-label="Bold">
755
+ <BoldIcon />
756
+ </Button>
757
+ <Button size="icon-sm" variant="ghost" aria-label="Italic">
758
+ <ItalicIcon />
759
+ </Button>
760
+ <Button size="icon-sm" variant="ghost" aria-label="Underline">
761
+ <UnderlineIcon />
762
+ </Button>
763
+ <div className="w-px bg-border mx-1" />
764
+ <Button size="icon-sm" variant="ghost" aria-label="Align left">
765
+ <AlignLeftIcon />
766
+ </Button>
767
+ <Button size="icon-sm" variant="ghost" aria-label="Align center">
768
+ <AlignCenterIcon />
769
+ </Button>
770
+ </div>
771
+
772
+ {/* Toolbar con ghost-neutral para apariencia más sutil */}
773
+ <div className="flex gap-1 p-2 border rounded-lg">
774
+ <Button size="icon-sm" variant="ghost-neutral" aria-label="Bold">
775
+ <BoldIcon />
776
+ </Button>
777
+ <Button size="icon-sm" variant="ghost-neutral" aria-label="Italic">
778
+ <ItalicIcon />
779
+ </Button>
780
+ <Button size="icon-sm" variant="ghost-neutral" aria-label="Underline">
781
+ <UnderlineIcon />
782
+ </Button>
783
+ </div>
784
+ ```
785
+
786
+ ### Call to Action (CTA)
787
+
788
+ Botón prominente en landing pages.
789
+
790
+ ```tsx
791
+ <Button size="lg" className="text-base px-8">
792
+ Get Started Free
793
+ <ArrowRightIcon />
794
+ </Button>
795
+ ```
796
+
797
+ ### Navegación
798
+
799
+ Botones como links de navegación.
800
+
801
+ ```tsx
802
+ import { useNavigate } from "react-router-dom";
803
+
804
+ function Navigation() {
805
+ const navigate = useNavigate();
806
+
807
+ return (
808
+ <div className="flex gap-2">
809
+ <Button variant="ghost" onClick={() => navigate("/home")}>
810
+ Home
811
+ </Button>
812
+ <Button variant="ghost" onClick={() => navigate("/about")}>
813
+ About
814
+ </Button>
815
+ <Button variant="ghost" onClick={() => navigate("/contact")}>
816
+ Contact
817
+ </Button>
818
+ </div>
819
+ );
820
+ }
821
+ ```
822
+
823
+ ### Botones de Paginación
824
+
825
+ ```tsx
826
+ <div className="flex items-center gap-2">
827
+ <Button size="icon-sm" variant="outline" disabled={currentPage === 1}>
828
+ <ChevronLeftIcon />
829
+ </Button>
830
+ <span className="text-sm">
831
+ Page {currentPage} of {totalPages}
832
+ </span>
833
+ <Button
834
+ size="icon-sm"
835
+ variant="outline"
836
+ disabled={currentPage === totalPages}
837
+ >
838
+ <ChevronRightIcon />
839
+ </Button>
840
+ </div>
841
+ ```
842
+
843
+ ## Mejores Prácticas
844
+
845
+ ### Jerarquía Visual
846
+
847
+ ```tsx
848
+ {
849
+ /* ✅ Correcto - Jerarquía clara */
850
+ }
851
+ <div className="flex gap-2">
852
+ <Button variant="outline">Cancel</Button>
853
+ <Button>Save</Button> {/* Acción primaria más prominente */}
854
+ </div>;
855
+
856
+ {
857
+ /* ❌ Incorrecto - Todas las acciones con igual énfasis */
858
+ }
859
+ <div className="flex gap-2">
860
+ <Button>Cancel</Button>
861
+ <Button>Save</Button>
862
+ </div>;
863
+ ```
864
+
865
+ ### Textos Descriptivos
866
+
867
+ ```tsx
868
+ {/* ✅ Correcto - Texto claro */}
869
+ <Button>Save Changes</Button>
870
+ <Button variant="destructive">Delete Account</Button>
871
+
872
+ {/* ❌ Incorrecto - Texto genérico */}
873
+ <Button>OK</Button>
874
+ <Button>Submit</Button>
875
+ ```
876
+
877
+ ### aria-label en Botones de Icono
878
+
879
+ ```tsx
880
+ {
881
+ /* ✅ Correcto - Accesible */
882
+ }
883
+ <Button size="icon" aria-label="Settings">
884
+ <SettingsIcon />
885
+ </Button>;
886
+
887
+ {
888
+ /* ❌ Incorrecto - No accesible */
889
+ }
890
+ <Button size="icon">
891
+ <SettingsIcon />
892
+ </Button>;
893
+ ```
894
+
895
+ ### Loading State
896
+
897
+ ```tsx
898
+ {
899
+ /* ✅ Correcto - Estado de carga explícito */
900
+ }
901
+ <Button disabled={isLoading}>
902
+ {isLoading && <Loader2Icon className="animate-spin" />}
903
+ {isLoading ? "Saving..." : "Save"}
904
+ </Button>;
905
+
906
+ {
907
+ /* ❌ Incorrecto - Solo disabled sin feedback visual */
908
+ }
909
+ <Button disabled={isLoading}>Save</Button>;
910
+ ```
911
+
912
+ ### Variantes Semánticas
913
+
914
+ ```tsx
915
+ {/* ✅ Correcto - Variantes apropiadas */}
916
+ <Button variant="success">Approve</Button>
917
+ <Button variant="destructive">Delete</Button>
918
+ <Button variant="warning">Proceed with Caution</Button>
919
+
920
+ {/* ❌ Incorrecto - Variantes genéricas para acciones críticas */}
921
+ <Button>Delete All Data</Button> {/* Debería ser destructive */}
922
+ ```
923
+
924
+ ### Ghost vs Ghost-Neutral
925
+
926
+ ```tsx
927
+ {/* ✅ Correcto - Ghost para acciones con énfasis primario */}
928
+ <Button variant="ghost">
929
+ <Icon symbol="add" />
930
+ Add Item
931
+ </Button>
932
+
933
+ {/* ✅ Correcto - Ghost-neutral para controles sin énfasis */}
934
+ <Button variant="ghost-neutral" size="icon" aria-label="Settings">
935
+ <Icon symbol="settings" />
936
+ </Button>
937
+
938
+ {/* ✅ Correcto - Ghost-neutral en toolbars y paneles */}
939
+ <div className="flex gap-1">
940
+ <Button variant="ghost-neutral" size="icon-sm" aria-label="Edit">
941
+ <Icon symbol="edit" />
942
+ </Button>
943
+ <Button variant="ghost-neutral" size="icon-sm" aria-label="Delete">
944
+ <Icon symbol="delete" />
945
+ </Button>
946
+ </div>
947
+
948
+ {/* ⚠️ Considerar - Ghost puede ser muy prominente en algunos contextos */}
949
+ <Button variant="ghost">Close</Button> {/* Mejor usar ghost-neutral */}
950
+ ```
951
+
952
+ ### Espaciado Consistente
953
+
954
+ ```tsx
955
+ {
956
+ /* ✅ Correcto - Gap consistente */
957
+ }
958
+ <div className="flex gap-2">
959
+ <Button variant="outline">Back</Button>
960
+ <Button variant="outline">Skip</Button>
961
+ <Button>Next</Button>
962
+ </div>;
963
+
964
+ {
965
+ /* ✅ También correcto - Separar acciones por importancia */
966
+ }
967
+ <div className="flex justify-between">
968
+ <Button variant="outline">Cancel</Button>
969
+ <div className="flex gap-2">
970
+ <Button variant="outline">Save as Draft</Button>
971
+ <Button>Publish</Button>
972
+ </div>
973
+ </div>;
974
+ ```
975
+
976
+ ## Notas de Implementación
977
+
978
+ - Basado en CVA (class-variance-authority) para variants system
979
+ - Usa Radix UI Slot para la prop `asChild`
980
+ - Los iconos SVG sin clase `size-*` se auto-dimensionan a 16px
981
+ - Padding horizontal se ajusta automáticamente con `has-[>svg]` selector
982
+ - Focus ring usa `focus-visible` (solo teclado, no mouse)
983
+ - Transiciones en `all` para suavidad en todos los cambios
984
+ - Sistema de slots con `data-slot="button"` para identificación
985
+ - Todos los botones son `inline-flex` para alineación perfecta
986
+ - `shrink-0` evita que botones se encojan en layouts flex
987
+
988
+ ## Accesibilidad
989
+
990
+ ### Navegación por Teclado
991
+
992
+ - ✅ **Tab**: Navega al botón
993
+ - ✅ **Space/Enter**: Activa el botón
994
+ - ✅ **Escape**: Sale del focus (en algunos contextos)
995
+
996
+ ### Estados Visuales Claros
997
+
998
+ - ✅ Focus ring visible solo con teclado (`focus-visible`)
999
+ - ✅ Disabled state con opacity reducida
1000
+ - ✅ Hover state en todas las variantes
1001
+ - ✅ Contraste de colores cumple WCAG AA
1002
+
1003
+ ### ARIA Best Practices
1004
+
1005
+ ```tsx
1006
+ {/* ✅ Botón de icono con aria-label */}
1007
+ <Button size="icon" aria-label="Delete item">
1008
+ <TrashIcon />
1009
+ </Button>
1010
+
1011
+ {/* ✅ Botón con estado de carga */}
1012
+ <Button disabled={isLoading} aria-busy={isLoading}>
1013
+ {isLoading && <Loader2Icon className="animate-spin" />}
1014
+ {isLoading ? "Loading..." : "Submit"}
1015
+ </Button>
1016
+
1017
+ {/* ✅ Botón con estado pressed (toggle) */}
1018
+ <Button
1019
+ variant={isActive ? "default" : "outline"}
1020
+ aria-pressed={isActive}
1021
+ onClick={() => setIsActive(!isActive)}
1022
+ >
1023
+ {isActive ? "Active" : "Inactive"}
1024
+ </Button>
1025
+
1026
+ {/* ✅ Botón con descripción expandida */}
1027
+ <Button aria-describedby="delete-description">
1028
+ Delete Account
1029
+ </Button>
1030
+ <p id="delete-description" className="sr-only">
1031
+ This action cannot be undone. All your data will be permanently deleted.
1032
+ </p>
1033
+ ```
1034
+
1035
+ ### Type en Formularios
1036
+
1037
+ ```tsx
1038
+ {
1039
+ /* ✅ Correcto - type explícito */
1040
+ }
1041
+ <form onSubmit={handleSubmit}>
1042
+ <Button type="submit">Submit</Button>
1043
+ <Button type="button" onClick={handleCancel}>
1044
+ Cancel
1045
+ </Button>
1046
+ <Button type="reset">Reset Form</Button>
1047
+ </form>;
1048
+
1049
+ {
1050
+ /* ⚠️ Cuidado - sin type en form, default es submit */
1051
+ }
1052
+ <form>
1053
+ <Button>Click me</Button> {/* Se comporta como submit */}
1054
+ </form>;
1055
+ ```
1056
+
1057
+ ### Screen Readers
1058
+
1059
+ - ✅ Texto descriptivo en lugar de solo iconos
1060
+ - ✅ `aria-label` para botones de solo icono
1061
+ - ✅ `aria-busy` durante estados de carga
1062
+ - ✅ `aria-invalid` para estados de error
1063
+ - ✅ Disabled buttons se anuncian como "disabled" o "dimmed"
1064
+
1065
+ ## Troubleshooting
1066
+
1067
+ ### Botón No Se Ve Como Esperado
1068
+
1069
+ **Problema**: Los estilos no se aplican correctamente.
1070
+
1071
+ **Solución**:
1072
+
1073
+ ```tsx
1074
+ // ❌ Incorrecto - className sobrescribe variantes
1075
+ <Button className="bg-destructive">Click</Button>
1076
+
1077
+ // ✅ Correcto - Usa variantes predefinidas
1078
+ <Button variant="destructive">Click</Button>
1079
+
1080
+ // ✅ O combina correctamente
1081
+ <Button variant="destructive" className="rounded-full">Click</Button>
1082
+ ```
1083
+
1084
+ ### Icono No Se Auto-Dimensiona
1085
+
1086
+ **Problema**: El icono es muy grande o muy pequeño.
1087
+
1088
+ **Solución**:
1089
+
1090
+ ```tsx
1091
+ // ❌ Incorrecto - clase size-* evita auto-dimensionado
1092
+ <Button>
1093
+ <SaveIcon className="size-8" /> {/* Muy grande */}
1094
+ Save
1095
+ </Button>
1096
+
1097
+ // ✅ Correcto - sin clase size-*
1098
+ <Button>
1099
+ <SaveIcon /> {/* Auto-dimensionado a 16px */}
1100
+ Save
1101
+ </Button>
1102
+
1103
+ // ✅ O especifica tamaño personalizado si es necesario
1104
+ <Button>
1105
+ <SaveIcon className="h-5 w-5" />
1106
+ Save
1107
+ </Button>
1108
+ ```
1109
+
1110
+ ### asChild No Funciona
1111
+
1112
+ **Problema**: El hijo no recibe los estilos.
1113
+
1114
+ **Solución**:
1115
+
1116
+ ```tsx
1117
+ // ❌ Incorrecto - múltiples hijos
1118
+ <Button asChild>
1119
+ <a href="/link">Click</a>
1120
+ <span>Extra</span>
1121
+ </Button>
1122
+
1123
+ // ✅ Correcto - un solo hijo directo
1124
+ <Button asChild>
1125
+ <a href="/link">
1126
+ <LinkIcon />
1127
+ Click Me
1128
+ </a>
1129
+ </Button>
1130
+ ```
1131
+
1132
+ ## Referencias
1133
+
1134
+ - CVA (Class Variance Authority): https://cva.style/docs
1135
+ - Radix UI Slot: https://www.radix-ui.com/primitives/docs/utilities/slot
1136
+ - shadcn/ui Button: https://ui.shadcn.com/docs/components/button
1137
+ - WCAG 2.1 Button Guidelines: https://www.w3.org/WAI/ARIA/apg/patterns/button/