@adamosuiteservices/ui 2.11.20 → 2.12.0

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