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