@adamosuiteservices/ui 2.11.17 → 2.11.19
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/badge.cjs +3 -3
- package/dist/badge.js +2 -2
- package/dist/breadcrumb.cjs +5 -5
- package/dist/breadcrumb.js +16 -16
- package/dist/components/ui/date-picker-selector/date-picker-selector.d.ts +2 -2
- package/dist/components/ui/sheet/sheet.d.ts +2 -1
- package/dist/date-picker-selector.cjs +1 -1
- package/dist/date-picker-selector.js +29 -30
- package/dist/dialog.cjs +1 -1
- package/dist/dialog.js +1 -1
- package/dist/select.cjs +2 -2
- package/dist/select.js +2 -2
- package/dist/{sheet-DVT_djHX.cjs → sheet-CvcCaGSl.cjs} +11 -11
- package/dist/{sheet-CPf9Guon.js → sheet-IRIc3TJ1.js} +33 -19
- package/dist/sheet.cjs +1 -1
- package/dist/sheet.js +7 -6
- package/dist/sidebar.cjs +1 -1
- package/dist/sidebar.js +1 -1
- package/dist/styles.css +1 -1
- package/dist/tabs-underline.cjs +3 -3
- package/dist/tabs-underline.js +13 -13
- package/dist/tabs.cjs +5 -4
- package/dist/tabs.js +3 -2
- package/dist/themes.css +1 -1
- package/docs/AI-GUIDE.md +321 -321
- package/docs/components/layout/sidebar.md +399 -399
- package/docs/components/layout/toaster.md +436 -436
- package/docs/components/ui/accordion-rounded.md +584 -584
- package/docs/components/ui/accordion.md +269 -269
- package/docs/components/ui/badge.md +2 -1
- package/docs/components/ui/button-group.md +984 -984
- package/docs/components/ui/button.md +1137 -1137
- package/docs/components/ui/calendar.md +1159 -1159
- package/docs/components/ui/card.md +1455 -1455
- package/docs/components/ui/checkbox.md +292 -292
- package/docs/components/ui/collapsible.md +323 -323
- package/docs/components/ui/command.md +454 -454
- package/docs/components/ui/context-menu.md +540 -540
- package/docs/components/ui/date-picker-selector.md +0 -2
- package/docs/components/ui/dialog.md +628 -628
- package/docs/components/ui/dropdown-menu.md +709 -709
- package/docs/components/ui/field.md +706 -706
- package/docs/components/ui/hover-card.md +446 -446
- package/docs/components/ui/input.md +362 -362
- package/docs/components/ui/kbd.md +434 -434
- package/docs/components/ui/label.md +359 -359
- package/docs/components/ui/pagination.md +650 -650
- package/docs/components/ui/popover.md +536 -536
- package/docs/components/ui/progress.md +182 -182
- package/docs/components/ui/radio-group.md +311 -311
- package/docs/components/ui/select.md +352 -352
- package/docs/components/ui/separator.md +214 -214
- package/docs/components/ui/sheet.md +174 -142
- package/docs/components/ui/skeleton.md +140 -140
- package/docs/components/ui/slider.md +341 -341
- package/docs/components/ui/spinner.md +170 -170
- package/docs/components/ui/switch.md +408 -408
- package/docs/components/ui/tabs-underline.md +106 -106
- package/docs/components/ui/tabs.md +122 -122
- package/docs/components/ui/textarea.md +243 -243
- package/docs/components/ui/toggle.md +237 -237
- package/docs/components/ui/tooltip.md +317 -317
- package/docs/components/ui/typography.md +280 -280
- package/package.json +1 -1
|
@@ -1,984 +1,984 @@
|
|
|
1
|
-
# Button Group Component
|
|
2
|
-
|
|
3
|
-
## Descripción
|
|
4
|
-
|
|
5
|
-
Componente para **agrupar botones relacionados visualmente**, creando una interfaz cohesiva donde múltiples acciones están conectadas. Soporta orientación horizontal y vertical, separadores visuales, anidamiento de grupos, integración con inputs/selects, texto descriptivo, y manejo automático de bordes/esquinas. Ideal para toolbars, controles de medios, formateo de texto y acciones relacionadas.
|
|
6
|
-
|
|
7
|
-
## Características
|
|
8
|
-
|
|
9
|
-
- ✅ 2 orientaciones: horizontal (default) y vertical
|
|
10
|
-
- ✅ Separadores visuales entre botones (ButtonGroupSeparator)
|
|
11
|
-
- ✅ Texto descriptivo dentro del grupo (ButtonGroupText)
|
|
12
|
-
- ✅ Soporte para grupos anidados con spacing automático
|
|
13
|
-
- ✅ Integración con Input y Select
|
|
14
|
-
- ✅ Bordes y esquinas automáticamente ajustados
|
|
15
|
-
- ✅ Focus z-index para overlapping correcto
|
|
16
|
-
- ✅ Split button pattern (acción + dropdown)
|
|
17
|
-
- ✅ Compatible con todos los tamaños de Button
|
|
18
|
-
- ✅ Role="group" para accesibilidad
|
|
19
|
-
- ✅ Width auto-ajustable (w-fit)
|
|
20
|
-
|
|
21
|
-
## Importación
|
|
22
|
-
|
|
23
|
-
```typescript
|
|
24
|
-
import {
|
|
25
|
-
ButtonGroup,
|
|
26
|
-
ButtonGroupSeparator,
|
|
27
|
-
ButtonGroupText,
|
|
28
|
-
} from "@adamosuiteservices/ui/button-group";
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
## Uso Básico
|
|
32
|
-
|
|
33
|
-
### Grupo Horizontal Simple
|
|
34
|
-
|
|
35
|
-
```tsx
|
|
36
|
-
import { Button } from "@adamosuiteservices/ui/button";
|
|
37
|
-
|
|
38
|
-
<ButtonGroup>
|
|
39
|
-
<Button variant="outline">Archive</Button>
|
|
40
|
-
<Button variant="outline">Report</Button>
|
|
41
|
-
<Button variant="outline">Snooze</Button>
|
|
42
|
-
</ButtonGroup>;
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
**Comportamiento**: Los botones se conectan visualmente eliminando bordes internos y redondeando solo las esquinas externas.
|
|
46
|
-
|
|
47
|
-
### Con Separadores
|
|
48
|
-
|
|
49
|
-
```tsx
|
|
50
|
-
<ButtonGroup>
|
|
51
|
-
<Button variant="secondary" size="sm">
|
|
52
|
-
Copy
|
|
53
|
-
</Button>
|
|
54
|
-
<ButtonGroupSeparator />
|
|
55
|
-
<Button variant="secondary" size="sm">
|
|
56
|
-
Paste
|
|
57
|
-
</Button>
|
|
58
|
-
</ButtonGroup>
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
**Uso**: Los separadores son útiles con variantes no-outline (secondary, default, etc.) para mejorar la separación visual.
|
|
62
|
-
|
|
63
|
-
### Orientación Vertical
|
|
64
|
-
|
|
65
|
-
```tsx
|
|
66
|
-
import { Icon } from "@adamosuiteservices/ui/icon";
|
|
67
|
-
|
|
68
|
-
<ButtonGroup orientation="vertical" className="h-fit">
|
|
69
|
-
<Button variant="outline" size="icon">
|
|
70
|
-
<Icon symbol="add" />
|
|
71
|
-
</Button>
|
|
72
|
-
<Button variant="outline" size="icon">
|
|
73
|
-
<Icon symbol="remove" />
|
|
74
|
-
</Button>
|
|
75
|
-
</ButtonGroup>;
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
**Nota**: Agrega `className="h-fit"` para evitar que el grupo se estire verticalmente.
|
|
79
|
-
|
|
80
|
-
## Componentes
|
|
81
|
-
|
|
82
|
-
### ButtonGroup (Root)
|
|
83
|
-
|
|
84
|
-
Contenedor principal que agrupa botones.
|
|
85
|
-
|
|
86
|
-
| Prop | Tipo | Default | Descripción |
|
|
87
|
-
| ----------- | -------------------------------- | -------------- | -------------------------------------------- |
|
|
88
|
-
| orientation | `"horizontal" \| "vertical"` | `"horizontal"` | Orientación del grupo |
|
|
89
|
-
| className | `string` | - | Clases CSS adicionales |
|
|
90
|
-
| children | `ReactNode` | - | Botones, separadores, texto, inputs, selects |
|
|
91
|
-
| ...props | `HTMLAttributes<HTMLDivElement>` | - | Props nativas de div |
|
|
92
|
-
|
|
93
|
-
**Atributos automáticos**:
|
|
94
|
-
|
|
95
|
-
- `role="group"`: Para accesibilidad
|
|
96
|
-
- `data-slot="button-group"`: Identificación interna
|
|
97
|
-
- `data-orientation`: Valor de orientation prop
|
|
98
|
-
|
|
99
|
-
**Estilos aplicados**:
|
|
100
|
-
|
|
101
|
-
- `flex`: Display flex
|
|
102
|
-
- `w-fit`: Ancho auto-ajustable
|
|
103
|
-
- `items-stretch`: Estira items verticalmente
|
|
104
|
-
- Bordes y esquinas ajustados automáticamente según orientation
|
|
105
|
-
|
|
106
|
-
### ButtonGroupSeparator
|
|
107
|
-
|
|
108
|
-
Línea separadora visual entre botones.
|
|
109
|
-
|
|
110
|
-
| Prop | Tipo | Default | Descripción |
|
|
111
|
-
| ----------- | ---------------------------- | ------------ | ------------------------------ |
|
|
112
|
-
| orientation | `"horizontal" \| "vertical"` | `"vertical"` | Orientación del separador |
|
|
113
|
-
| className | `string` | - | Clases CSS adicionales |
|
|
114
|
-
| ...props | Props de Separator | - | Props del componente Separator |
|
|
115
|
-
|
|
116
|
-
**Estilos aplicados**:
|
|
117
|
-
|
|
118
|
-
- `bg-input`: Color de fondo
|
|
119
|
-
- `relative`: Posicionamiento
|
|
120
|
-
- `!m-0`: Sin márgenes
|
|
121
|
-
- `self-stretch`: Se estira para llenar altura/ancho
|
|
122
|
-
- `h-auto` cuando orientation="vertical"
|
|
123
|
-
|
|
124
|
-
**Nota**: Usa `orientation="vertical"` (default) para grupos horizontales, y `orientation="horizontal"` para grupos verticales.
|
|
125
|
-
|
|
126
|
-
### ButtonGroupText
|
|
127
|
-
|
|
128
|
-
Texto descriptivo dentro del grupo.
|
|
129
|
-
|
|
130
|
-
| Prop | Tipo | Default | Descripción |
|
|
131
|
-
| --------- | -------------------------------- | ------- | --------------------------------- |
|
|
132
|
-
| asChild | `boolean` | `false` | Renderiza el hijo en lugar de div |
|
|
133
|
-
| className | `string` | - | Clases CSS adicionales |
|
|
134
|
-
| children | `ReactNode` | - | Texto o contenido |
|
|
135
|
-
| ...props | `HTMLAttributes<HTMLDivElement>` | - | Props nativas de div |
|
|
136
|
-
|
|
137
|
-
**Estilos aplicados**:
|
|
138
|
-
|
|
139
|
-
- `bg-muted`: Fondo gris claro
|
|
140
|
-
- `flex items-center gap-2`: Layout flex centrado
|
|
141
|
-
- `rounded-md border`: Bordes redondeados
|
|
142
|
-
- `px-4`: Padding horizontal
|
|
143
|
-
- `text-sm font-medium`: Tipografía
|
|
144
|
-
- `shadow-xs`: Sombra sutil
|
|
145
|
-
- Iconos auto-dimensionados a 16px
|
|
146
|
-
|
|
147
|
-
## Orientaciones
|
|
148
|
-
|
|
149
|
-
### horizontal (Default)
|
|
150
|
-
|
|
151
|
-
Botones en fila horizontal.
|
|
152
|
-
|
|
153
|
-
```tsx
|
|
154
|
-
<ButtonGroup orientation="horizontal">
|
|
155
|
-
<Button variant="outline">First</Button>
|
|
156
|
-
<Button variant="outline">Second</Button>
|
|
157
|
-
<Button variant="outline">Third</Button>
|
|
158
|
-
</ButtonGroup>
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
**Estilos automáticos**:
|
|
162
|
-
|
|
163
|
-
- `:not(:first-child)`: `rounded-l-none border-l-0` (elimina esquinas izquierdas y borde)
|
|
164
|
-
- `:not(:last-child)`: `rounded-r-none` (elimina esquinas derechas)
|
|
165
|
-
|
|
166
|
-
### vertical
|
|
167
|
-
|
|
168
|
-
Botones apilados verticalmente.
|
|
169
|
-
|
|
170
|
-
```tsx
|
|
171
|
-
<ButtonGroup orientation="vertical" className="h-fit">
|
|
172
|
-
<Button variant="outline">First</Button>
|
|
173
|
-
<Button variant="outline">Second</Button>
|
|
174
|
-
<Button variant="outline">Third</Button>
|
|
175
|
-
</ButtonGroup>
|
|
176
|
-
```
|
|
177
|
-
|
|
178
|
-
**Estilos automáticos**:
|
|
179
|
-
|
|
180
|
-
- `flex-col`: Dirección vertical
|
|
181
|
-
- `:not(:first-child)`: `rounded-t-none border-t-0` (elimina esquinas superiores y borde)
|
|
182
|
-
- `:not(:last-child)`: `rounded-b-none` (elimina esquinas inferiores)
|
|
183
|
-
|
|
184
|
-
## Tamaños
|
|
185
|
-
|
|
186
|
-
Todos los tamaños de Button funcionan en ButtonGroup:
|
|
187
|
-
|
|
188
|
-
```tsx
|
|
189
|
-
{
|
|
190
|
-
/* Small */
|
|
191
|
-
}
|
|
192
|
-
<ButtonGroup>
|
|
193
|
-
<Button variant="outline" size="sm">
|
|
194
|
-
Small
|
|
195
|
-
</Button>
|
|
196
|
-
<Button variant="outline" size="sm">
|
|
197
|
-
Buttons
|
|
198
|
-
</Button>
|
|
199
|
-
<Button variant="outline" size="icon-sm">
|
|
200
|
-
<Icon symbol="add" />
|
|
201
|
-
</Button>
|
|
202
|
-
</ButtonGroup>;
|
|
203
|
-
|
|
204
|
-
{
|
|
205
|
-
/* Default */
|
|
206
|
-
}
|
|
207
|
-
<ButtonGroup>
|
|
208
|
-
<Button variant="outline">Default</Button>
|
|
209
|
-
<Button variant="outline">Buttons</Button>
|
|
210
|
-
<Button variant="outline" size="icon">
|
|
211
|
-
<Icon symbol="add" />
|
|
212
|
-
</Button>
|
|
213
|
-
</ButtonGroup>;
|
|
214
|
-
|
|
215
|
-
{
|
|
216
|
-
/* Large */
|
|
217
|
-
}
|
|
218
|
-
<ButtonGroup>
|
|
219
|
-
<Button variant="outline" size="lg">
|
|
220
|
-
Large
|
|
221
|
-
</Button>
|
|
222
|
-
<Button variant="outline" size="lg">
|
|
223
|
-
Buttons
|
|
224
|
-
</Button>
|
|
225
|
-
<Button variant="outline" size="icon-lg">
|
|
226
|
-
<Icon symbol="add" />
|
|
227
|
-
</Button>
|
|
228
|
-
</ButtonGroup>;
|
|
229
|
-
```
|
|
230
|
-
|
|
231
|
-
**Regla importante**: Mantén tamaños consistentes dentro del mismo grupo.
|
|
232
|
-
|
|
233
|
-
## Patrones Avanzados
|
|
234
|
-
|
|
235
|
-
### Grupos Anidados
|
|
236
|
-
|
|
237
|
-
Para crear toolbars complejas con espaciado entre grupos lógicos.
|
|
238
|
-
|
|
239
|
-
```tsx
|
|
240
|
-
<ButtonGroup>
|
|
241
|
-
<ButtonGroup>
|
|
242
|
-
<Button variant="outline" size="sm">
|
|
243
|
-
1
|
|
244
|
-
</Button>
|
|
245
|
-
<Button variant="outline" size="sm">
|
|
246
|
-
2
|
|
247
|
-
</Button>
|
|
248
|
-
<Button variant="outline" size="sm">
|
|
249
|
-
3
|
|
250
|
-
</Button>
|
|
251
|
-
</ButtonGroup>
|
|
252
|
-
<ButtonGroup>
|
|
253
|
-
<Button variant="outline" size="icon-sm">
|
|
254
|
-
<Icon symbol="arrow_back" />
|
|
255
|
-
</Button>
|
|
256
|
-
<Button variant="outline" size="icon-sm">
|
|
257
|
-
<Icon symbol="arrow_forward" />
|
|
258
|
-
</Button>
|
|
259
|
-
</ButtonGroup>
|
|
260
|
-
</ButtonGroup>
|
|
261
|
-
```
|
|
262
|
-
|
|
263
|
-
**Comportamiento**: Los grupos anidados tienen `gap-2` automático entre ellos.
|
|
264
|
-
|
|
265
|
-
### Split Button
|
|
266
|
-
|
|
267
|
-
Botón con acción principal y menú dropdown.
|
|
268
|
-
|
|
269
|
-
```tsx
|
|
270
|
-
<ButtonGroup>
|
|
271
|
-
<Button variant="secondary">Deploy</Button>
|
|
272
|
-
<ButtonGroupSeparator />
|
|
273
|
-
<Button size="icon" variant="secondary">
|
|
274
|
-
<Icon symbol="keyboard_arrow_down" />
|
|
275
|
-
</Button>
|
|
276
|
-
</ButtonGroup>
|
|
277
|
-
```
|
|
278
|
-
|
|
279
|
-
**Uso común**: Acción por defecto + opciones adicionales en dropdown.
|
|
280
|
-
|
|
281
|
-
### Con Input (Search Box)
|
|
282
|
-
|
|
283
|
-
```tsx
|
|
284
|
-
import { Input } from "@adamosuiteservices/ui/input";
|
|
285
|
-
import { Icon } from "@adamosuiteservices/ui/icon";
|
|
286
|
-
|
|
287
|
-
<ButtonGroup>
|
|
288
|
-
<Input placeholder="Search..." />
|
|
289
|
-
<Button variant="outline" aria-label="Search">
|
|
290
|
-
<Icon symbol="search" />
|
|
291
|
-
</Button>
|
|
292
|
-
</ButtonGroup>;
|
|
293
|
-
```
|
|
294
|
-
|
|
295
|
-
**Comportamiento**: El input se integra visualmente con el botón, creando una barra de búsqueda cohesiva.
|
|
296
|
-
|
|
297
|
-
### Con Select (Currency Selector)
|
|
298
|
-
|
|
299
|
-
```tsx
|
|
300
|
-
import { Input } from "@adamosuiteservices/ui/input";
|
|
301
|
-
import {
|
|
302
|
-
Select,
|
|
303
|
-
SelectTrigger,
|
|
304
|
-
SelectContent,
|
|
305
|
-
SelectItem,
|
|
306
|
-
} from "@adamosuiteservices/ui/select";
|
|
307
|
-
import { Icon } from "@adamosuiteservices/ui/icon";
|
|
308
|
-
|
|
309
|
-
function CurrencyInput() {
|
|
310
|
-
const [currency, setCurrency] = useState("$");
|
|
311
|
-
|
|
312
|
-
return (
|
|
313
|
-
<ButtonGroup>
|
|
314
|
-
<ButtonGroup>
|
|
315
|
-
<Select value={currency} onValueChange={setCurrency}>
|
|
316
|
-
<SelectTrigger className="font-mono">{currency}</SelectTrigger>
|
|
317
|
-
<SelectContent className="min-w-24">
|
|
318
|
-
<SelectItem value="$">$ US Dollar</SelectItem>
|
|
319
|
-
<SelectItem value="€">€ Euro</SelectItem>
|
|
320
|
-
<SelectItem value="£">£ British Pound</SelectItem>
|
|
321
|
-
</SelectContent>
|
|
322
|
-
</Select>
|
|
323
|
-
<Input placeholder="10.00" pattern="[0-9]*" />
|
|
324
|
-
</ButtonGroup>
|
|
325
|
-
<ButtonGroup>
|
|
326
|
-
<Button aria-label="Send" size="icon" variant="outline">
|
|
327
|
-
<Icon symbol="arrow_forward" />
|
|
328
|
-
</Button>
|
|
329
|
-
</ButtonGroup>
|
|
330
|
-
</ButtonGroup>
|
|
331
|
-
);
|
|
332
|
-
}
|
|
333
|
-
```
|
|
334
|
-
|
|
335
|
-
### Controles de Medios
|
|
336
|
-
|
|
337
|
-
Player de audio/video con controles agrupados.
|
|
338
|
-
|
|
339
|
-
```tsx
|
|
340
|
-
import { Icon } from "@adamosuiteservices/ui/icon";
|
|
341
|
-
|
|
342
|
-
function MediaControls() {
|
|
343
|
-
const [isPlaying, setIsPlaying] = useState(false);
|
|
344
|
-
|
|
345
|
-
return (
|
|
346
|
-
<div className="space-y-4">
|
|
347
|
-
{/* Controles de reproducción */}
|
|
348
|
-
<ButtonGroup>
|
|
349
|
-
<Button variant="outline" size="icon">
|
|
350
|
-
<Icon symbol="skip_previous" />
|
|
351
|
-
</Button>
|
|
352
|
-
<Button
|
|
353
|
-
variant="outline"
|
|
354
|
-
size="icon"
|
|
355
|
-
onClick={() => setIsPlaying(!isPlaying)}
|
|
356
|
-
>
|
|
357
|
-
{isPlaying ? <Icon symbol="pause" /> : <Icon symbol="play_arrow" />}
|
|
358
|
-
</Button>
|
|
359
|
-
<Button variant="outline" size="icon">
|
|
360
|
-
<Icon symbol="stop" />
|
|
361
|
-
</Button>
|
|
362
|
-
<Button variant="outline" size="icon">
|
|
363
|
-
<Icon symbol="skip_next" />
|
|
364
|
-
</Button>
|
|
365
|
-
</ButtonGroup>
|
|
366
|
-
|
|
367
|
-
{/* Controles de volumen */}
|
|
368
|
-
<ButtonGroup>
|
|
369
|
-
<Button variant="outline" size="icon">
|
|
370
|
-
<Icon symbol="volume_off" />
|
|
371
|
-
</Button>
|
|
372
|
-
<ButtonGroupText className="px-4">Volume</ButtonGroupText>
|
|
373
|
-
<Button variant="outline" size="icon">
|
|
374
|
-
<Icon symbol="volume_up" />
|
|
375
|
-
</Button>
|
|
376
|
-
</ButtonGroup>
|
|
377
|
-
</div>
|
|
378
|
-
);
|
|
379
|
-
}
|
|
380
|
-
```
|
|
381
|
-
|
|
382
|
-
### Editor de Texto (Toolbar)
|
|
383
|
-
|
|
384
|
-
Controles de formateo con estados toggle.
|
|
385
|
-
|
|
386
|
-
```tsx
|
|
387
|
-
import { Icon } from "@adamosuiteservices/ui/icon";
|
|
388
|
-
|
|
389
|
-
function TextEditor() {
|
|
390
|
-
const [formatting, setFormatting] = useState({
|
|
391
|
-
bold: false,
|
|
392
|
-
italic: false,
|
|
393
|
-
underline: false,
|
|
394
|
-
});
|
|
395
|
-
const [alignment, setAlignment] = useState("left");
|
|
396
|
-
|
|
397
|
-
return (
|
|
398
|
-
<div className="space-y-4">
|
|
399
|
-
{/* Formato de texto */}
|
|
400
|
-
<ButtonGroup>
|
|
401
|
-
<Button
|
|
402
|
-
variant={formatting.bold ? "default" : "outline"}
|
|
403
|
-
size="icon"
|
|
404
|
-
onClick={() =>
|
|
405
|
-
setFormatting((prev) => ({ ...prev, bold: !prev.bold }))
|
|
406
|
-
}
|
|
407
|
-
>
|
|
408
|
-
<Icon symbol="format_bold" />
|
|
409
|
-
</Button>
|
|
410
|
-
<Button
|
|
411
|
-
variant={formatting.italic ? "default" : "outline"}
|
|
412
|
-
size="icon"
|
|
413
|
-
onClick={() =>
|
|
414
|
-
setFormatting((prev) => ({ ...prev, italic: !prev.italic }))
|
|
415
|
-
}
|
|
416
|
-
>
|
|
417
|
-
<Icon symbol="format_italic" />
|
|
418
|
-
</Button>
|
|
419
|
-
<Button
|
|
420
|
-
variant={formatting.underline ? "default" : "outline"}
|
|
421
|
-
size="icon"
|
|
422
|
-
onClick={() =>
|
|
423
|
-
setFormatting((prev) => ({ ...prev, underline: !prev.underline }))
|
|
424
|
-
}
|
|
425
|
-
>
|
|
426
|
-
<Icon symbol="format_underlined" />
|
|
427
|
-
</Button>
|
|
428
|
-
</ButtonGroup>
|
|
429
|
-
|
|
430
|
-
{/* Alineación */}
|
|
431
|
-
<ButtonGroup>
|
|
432
|
-
<Button
|
|
433
|
-
variant={alignment === "left" ? "default" : "outline"}
|
|
434
|
-
size="icon"
|
|
435
|
-
onClick={() => setAlignment("left")}
|
|
436
|
-
>
|
|
437
|
-
<Icon symbol="format_align_left" />
|
|
438
|
-
</Button>
|
|
439
|
-
<Button
|
|
440
|
-
variant={alignment === "center" ? "default" : "outline"}
|
|
441
|
-
size="icon"
|
|
442
|
-
onClick={() => setAlignment("center")}
|
|
443
|
-
>
|
|
444
|
-
<Icon symbol="format_align_center" />
|
|
445
|
-
</Button>
|
|
446
|
-
<Button
|
|
447
|
-
variant={alignment === "right" ? "default" : "outline"}
|
|
448
|
-
size="icon"
|
|
449
|
-
onClick={() => setAlignment("right")}
|
|
450
|
-
>
|
|
451
|
-
<Icon symbol="format_align_right" />
|
|
452
|
-
</Button>
|
|
453
|
-
</ButtonGroup>
|
|
454
|
-
</div>
|
|
455
|
-
);
|
|
456
|
-
}
|
|
457
|
-
```
|
|
458
|
-
|
|
459
|
-
### Acciones de Archivo
|
|
460
|
-
|
|
461
|
-
Grupos para operaciones de archivo.
|
|
462
|
-
|
|
463
|
-
```tsx
|
|
464
|
-
import { Icon } from "@adamosuiteservices/ui/icon";
|
|
465
|
-
|
|
466
|
-
<div className="space-y-4">
|
|
467
|
-
{/* Acciones principales */}
|
|
468
|
-
<ButtonGroup>
|
|
469
|
-
<Button variant="outline">
|
|
470
|
-
<Icon symbol="save" />
|
|
471
|
-
Save
|
|
472
|
-
</Button>
|
|
473
|
-
<Button variant="outline">
|
|
474
|
-
<Icon symbol="download" />
|
|
475
|
-
Download
|
|
476
|
-
</Button>
|
|
477
|
-
<Button variant="outline">
|
|
478
|
-
<Icon symbol="upload" />
|
|
479
|
-
Upload
|
|
480
|
-
</Button>
|
|
481
|
-
</ButtonGroup>
|
|
482
|
-
|
|
483
|
-
{/* Acciones rápidas */}
|
|
484
|
-
<ButtonGroup>
|
|
485
|
-
<Button variant="outline" size="icon">
|
|
486
|
-
<Icon symbol="undo" />
|
|
487
|
-
</Button>
|
|
488
|
-
<Button variant="outline" size="icon">
|
|
489
|
-
<Icon symbol="redo" />
|
|
490
|
-
</Button>
|
|
491
|
-
<ButtonGroupSeparator />
|
|
492
|
-
<Button variant="outline" size="icon">
|
|
493
|
-
<Icon symbol="content_copy" />
|
|
494
|
-
</Button>
|
|
495
|
-
<Button variant="outline" size="icon">
|
|
496
|
-
<Icon symbol="share" />
|
|
497
|
-
</Button>
|
|
498
|
-
<ButtonGroupSeparator />
|
|
499
|
-
<Button variant="outline" size="icon">
|
|
500
|
-
<Icon symbol="delete" />
|
|
501
|
-
</Button>
|
|
502
|
-
</ButtonGroup>
|
|
503
|
-
</div>;
|
|
504
|
-
```
|
|
505
|
-
|
|
506
|
-
### Toolbar Compleja
|
|
507
|
-
|
|
508
|
-
Layout con múltiples grupos anidados.
|
|
509
|
-
|
|
510
|
-
```tsx
|
|
511
|
-
import { Icon } from "@adamosuiteservices/ui/icon";
|
|
512
|
-
|
|
513
|
-
<ButtonGroup>
|
|
514
|
-
<ButtonGroup>
|
|
515
|
-
<Button variant="outline" size="icon">
|
|
516
|
-
<Icon symbol="arrow_back" />
|
|
517
|
-
</Button>
|
|
518
|
-
</ButtonGroup>
|
|
519
|
-
<ButtonGroup>
|
|
520
|
-
<Button variant="outline">Archive</Button>
|
|
521
|
-
<Button variant="outline">Report</Button>
|
|
522
|
-
</ButtonGroup>
|
|
523
|
-
<ButtonGroup>
|
|
524
|
-
<Button variant="outline">Snooze</Button>
|
|
525
|
-
<Button variant="outline" size="icon">
|
|
526
|
-
<Icon symbol="more_horiz" />
|
|
527
|
-
</Button>
|
|
528
|
-
</ButtonGroup>
|
|
529
|
-
<ButtonGroup>
|
|
530
|
-
<Button variant="outline" size="icon">
|
|
531
|
-
<Icon symbol="settings" />
|
|
532
|
-
</Button>
|
|
533
|
-
<Button variant="outline" size="icon">
|
|
534
|
-
<Icon symbol="refresh" />
|
|
535
|
-
</Button>
|
|
536
|
-
</ButtonGroup>
|
|
537
|
-
</ButtonGroup>;
|
|
538
|
-
```
|
|
539
|
-
|
|
540
|
-
### Con Texto Descriptivo
|
|
541
|
-
|
|
542
|
-
Etiquetas para claridad contextual.
|
|
543
|
-
|
|
544
|
-
```tsx
|
|
545
|
-
import { Icon } from "@adamosuiteservices/ui/icon";
|
|
546
|
-
|
|
547
|
-
<div className="space-y-4">
|
|
548
|
-
{/* Texto antes */}
|
|
549
|
-
<ButtonGroup>
|
|
550
|
-
<ButtonGroupText>Actions:</ButtonGroupText>
|
|
551
|
-
<Button variant="outline">Edit</Button>
|
|
552
|
-
<Button variant="outline">Delete</Button>
|
|
553
|
-
</ButtonGroup>
|
|
554
|
-
|
|
555
|
-
{/* Texto entre botones */}
|
|
556
|
-
<ButtonGroup>
|
|
557
|
-
<Button variant="outline">
|
|
558
|
-
<Icon symbol="edit" />
|
|
559
|
-
Edit
|
|
560
|
-
</Button>
|
|
561
|
-
<ButtonGroupText>or</ButtonGroupText>
|
|
562
|
-
<Button variant="outline">
|
|
563
|
-
<Icon symbol="content_copy" />
|
|
564
|
-
Copy
|
|
565
|
-
</Button>
|
|
566
|
-
</ButtonGroup>
|
|
567
|
-
</div>;
|
|
568
|
-
```
|
|
569
|
-
|
|
570
|
-
## Casos de Uso Comunes
|
|
571
|
-
|
|
572
|
-
### Paginación
|
|
573
|
-
|
|
574
|
-
```tsx
|
|
575
|
-
import { Icon } from "@adamosuiteservices/ui/icon";
|
|
576
|
-
|
|
577
|
-
<ButtonGroup>
|
|
578
|
-
<Button variant="outline" size="icon" disabled={currentPage === 1}>
|
|
579
|
-
<Icon symbol="chevron_left" />
|
|
580
|
-
</Button>
|
|
581
|
-
<ButtonGroupText>
|
|
582
|
-
{currentPage} of {totalPages}
|
|
583
|
-
</ButtonGroupText>
|
|
584
|
-
<Button variant="outline" size="icon" disabled={currentPage === totalPages}>
|
|
585
|
-
<Icon symbol="chevron_right" />
|
|
586
|
-
</Button>
|
|
587
|
-
</ButtonGroup>;
|
|
588
|
-
```
|
|
589
|
-
|
|
590
|
-
### Filtros
|
|
591
|
-
|
|
592
|
-
```tsx
|
|
593
|
-
<ButtonGroup>
|
|
594
|
-
<ButtonGroupText>Filter:</ButtonGroupText>
|
|
595
|
-
<Button
|
|
596
|
-
variant={filter === "all" ? "default" : "outline"}
|
|
597
|
-
onClick={() => setFilter("all")}
|
|
598
|
-
>
|
|
599
|
-
All
|
|
600
|
-
</Button>
|
|
601
|
-
<Button
|
|
602
|
-
variant={filter === "active" ? "default" : "outline"}
|
|
603
|
-
onClick={() => setFilter("active")}
|
|
604
|
-
>
|
|
605
|
-
Active
|
|
606
|
-
</Button>
|
|
607
|
-
<Button
|
|
608
|
-
variant={filter === "archived" ? "default" : "outline"}
|
|
609
|
-
onClick={() => setFilter("archived")}
|
|
610
|
-
>
|
|
611
|
-
Archived
|
|
612
|
-
</Button>
|
|
613
|
-
</ButtonGroup>
|
|
614
|
-
```
|
|
615
|
-
|
|
616
|
-
### Tabs Alternativos
|
|
617
|
-
|
|
618
|
-
```tsx
|
|
619
|
-
<ButtonGroup>
|
|
620
|
-
<Button
|
|
621
|
-
variant={tab === "overview" ? "default" : "outline"}
|
|
622
|
-
onClick={() => setTab("overview")}
|
|
623
|
-
>
|
|
624
|
-
Overview
|
|
625
|
-
</Button>
|
|
626
|
-
<Button
|
|
627
|
-
variant={tab === "analytics" ? "default" : "outline"}
|
|
628
|
-
onClick={() => setTab("analytics")}
|
|
629
|
-
>
|
|
630
|
-
Analytics
|
|
631
|
-
</Button>
|
|
632
|
-
<Button
|
|
633
|
-
variant={tab === "reports" ? "default" : "outline"}
|
|
634
|
-
onClick={() => setTab("reports")}
|
|
635
|
-
>
|
|
636
|
-
Reports
|
|
637
|
-
</Button>
|
|
638
|
-
</ButtonGroup>
|
|
639
|
-
```
|
|
640
|
-
|
|
641
|
-
### Vista Toggle (List/Grid)
|
|
642
|
-
|
|
643
|
-
```tsx
|
|
644
|
-
import { Icon } from "@adamosuiteservices/ui/icon";
|
|
645
|
-
|
|
646
|
-
<ButtonGroup>
|
|
647
|
-
<Button
|
|
648
|
-
variant={view === "grid" ? "default" : "outline"}
|
|
649
|
-
size="icon"
|
|
650
|
-
onClick={() => setView("grid")}
|
|
651
|
-
aria-label="Grid view"
|
|
652
|
-
>
|
|
653
|
-
<Icon symbol="grid_view" />
|
|
654
|
-
</Button>
|
|
655
|
-
<Button
|
|
656
|
-
variant={view === "list" ? "default" : "outline"}
|
|
657
|
-
size="icon"
|
|
658
|
-
onClick={() => setView("list")}
|
|
659
|
-
aria-label="List view"
|
|
660
|
-
>
|
|
661
|
-
<Icon symbol="list" />
|
|
662
|
-
</Button>
|
|
663
|
-
</ButtonGroup>;
|
|
664
|
-
```
|
|
665
|
-
|
|
666
|
-
### Zoom Controls
|
|
667
|
-
|
|
668
|
-
```tsx
|
|
669
|
-
import { Icon } from "@adamosuiteservices/ui/icon";
|
|
670
|
-
|
|
671
|
-
<ButtonGroup>
|
|
672
|
-
<Button variant="outline" size="icon" onClick={handleZoomOut}>
|
|
673
|
-
<Icon symbol="zoom_out" />
|
|
674
|
-
</Button>
|
|
675
|
-
<ButtonGroupText className="px-6 font-mono">{zoom}%</ButtonGroupText>
|
|
676
|
-
<Button variant="outline" size="icon" onClick={handleZoomIn}>
|
|
677
|
-
<Icon symbol="zoom_in" />
|
|
678
|
-
</Button>
|
|
679
|
-
</ButtonGroup>;
|
|
680
|
-
```
|
|
681
|
-
|
|
682
|
-
## Mejores Prácticas
|
|
683
|
-
|
|
684
|
-
### Consistencia de Tamaños
|
|
685
|
-
|
|
686
|
-
```tsx
|
|
687
|
-
{
|
|
688
|
-
/* ✅ Correcto - Tamaños consistentes */
|
|
689
|
-
}
|
|
690
|
-
<ButtonGroup>
|
|
691
|
-
<Button variant="outline" size="sm">
|
|
692
|
-
One
|
|
693
|
-
</Button>
|
|
694
|
-
<Button variant="outline" size="sm">
|
|
695
|
-
Two
|
|
696
|
-
</Button>
|
|
697
|
-
<Button variant="outline" size="icon-sm">
|
|
698
|
-
<Icon symbol="add" />
|
|
699
|
-
</Button>
|
|
700
|
-
</ButtonGroup>;
|
|
701
|
-
|
|
702
|
-
{
|
|
703
|
-
/* ❌ Incorrecto - Mezcla de tamaños */
|
|
704
|
-
}
|
|
705
|
-
<ButtonGroup>
|
|
706
|
-
<Button variant="outline" size="sm">
|
|
707
|
-
One
|
|
708
|
-
</Button>
|
|
709
|
-
<Button variant="outline">Two</Button> {/* Tamaño diferente */}
|
|
710
|
-
<Button variant="outline" size="lg">
|
|
711
|
-
Three
|
|
712
|
-
</Button>
|
|
713
|
-
</ButtonGroup>;
|
|
714
|
-
```
|
|
715
|
-
|
|
716
|
-
### Variantes Consistentes
|
|
717
|
-
|
|
718
|
-
```tsx
|
|
719
|
-
{
|
|
720
|
-
/* ✅ Correcto - Misma variante */
|
|
721
|
-
}
|
|
722
|
-
<ButtonGroup>
|
|
723
|
-
<Button variant="outline">One</Button>
|
|
724
|
-
<Button variant="outline">Two</Button>
|
|
725
|
-
<Button variant="outline">Three</Button>
|
|
726
|
-
</ButtonGroup>;
|
|
727
|
-
|
|
728
|
-
{
|
|
729
|
-
/* ⚠️ Permitido - Variantes diferentes para estado activo */
|
|
730
|
-
}
|
|
731
|
-
<ButtonGroup>
|
|
732
|
-
<Button variant={isActive ? "default" : "outline"}>Active</Button>
|
|
733
|
-
<Button variant="outline">Inactive</Button>
|
|
734
|
-
</ButtonGroup>;
|
|
735
|
-
```
|
|
736
|
-
|
|
737
|
-
### Separadores con Variantes No-Outline
|
|
738
|
-
|
|
739
|
-
```tsx
|
|
740
|
-
{
|
|
741
|
-
/* ✅ Correcto - Separador con variante secondary */
|
|
742
|
-
}
|
|
743
|
-
<ButtonGroup>
|
|
744
|
-
<Button variant="secondary">Copy</Button>
|
|
745
|
-
<ButtonGroupSeparator />
|
|
746
|
-
<Button variant="secondary">Paste</Button>
|
|
747
|
-
</ButtonGroup>;
|
|
748
|
-
|
|
749
|
-
{
|
|
750
|
-
/* ⚠️ Innecesario - Separador con outline (ya hay bordes) */
|
|
751
|
-
}
|
|
752
|
-
<ButtonGroup>
|
|
753
|
-
<Button variant="outline">Copy</Button>
|
|
754
|
-
<ButtonGroupSeparator /> {/* No se ve bien */}
|
|
755
|
-
<Button variant="outline">Paste</Button>
|
|
756
|
-
</ButtonGroup>;
|
|
757
|
-
```
|
|
758
|
-
|
|
759
|
-
### aria-label en Botones de Icono
|
|
760
|
-
|
|
761
|
-
```tsx
|
|
762
|
-
{
|
|
763
|
-
/* ✅ Correcto - Labels descriptivas */
|
|
764
|
-
}
|
|
765
|
-
<ButtonGroup>
|
|
766
|
-
<Button variant="outline" size="icon" aria-label="Previous page">
|
|
767
|
-
<Icon symbol="arrow_back" />
|
|
768
|
-
</Button>
|
|
769
|
-
<Button variant="outline" size="icon" aria-label="Next page">
|
|
770
|
-
<Icon symbol="arrow_forward" />
|
|
771
|
-
</Button>
|
|
772
|
-
</ButtonGroup>;
|
|
773
|
-
|
|
774
|
-
{
|
|
775
|
-
/* ❌ Incorrecto - Sin labels */
|
|
776
|
-
}
|
|
777
|
-
<ButtonGroup>
|
|
778
|
-
<Button variant="outline" size="icon">
|
|
779
|
-
<Icon symbol="arrow_back" />
|
|
780
|
-
</Button>
|
|
781
|
-
<Button variant="outline" size="icon">
|
|
782
|
-
<Icon symbol="arrow_forward" />
|
|
783
|
-
</Button>
|
|
784
|
-
</ButtonGroup>;
|
|
785
|
-
```
|
|
786
|
-
|
|
787
|
-
### Grupos Anidados para Organización
|
|
788
|
-
|
|
789
|
-
```tsx
|
|
790
|
-
{
|
|
791
|
-
/* ✅ Correcto - Agrupación lógica */
|
|
792
|
-
}
|
|
793
|
-
<ButtonGroup>
|
|
794
|
-
<ButtonGroup>
|
|
795
|
-
{/* Acciones de navegación */}
|
|
796
|
-
<Button variant="outline" size="icon">
|
|
797
|
-
<Icon symbol="arrow_back" />
|
|
798
|
-
</Button>
|
|
799
|
-
<Button variant="outline" size="icon">
|
|
800
|
-
<Icon symbol="arrow_forward" />
|
|
801
|
-
</Button>
|
|
802
|
-
</ButtonGroup>
|
|
803
|
-
<ButtonGroup>
|
|
804
|
-
{/* Acciones de contenido */}
|
|
805
|
-
<Button variant="outline">Save</Button>
|
|
806
|
-
<Button variant="outline">Delete</Button>
|
|
807
|
-
</ButtonGroup>
|
|
808
|
-
</ButtonGroup>;
|
|
809
|
-
|
|
810
|
-
{
|
|
811
|
-
/* ❌ Incorrecto - Todo mezclado */
|
|
812
|
-
}
|
|
813
|
-
<ButtonGroup>
|
|
814
|
-
<Button variant="outline" size="icon">
|
|
815
|
-
<Icon symbol="arrow_back" />
|
|
816
|
-
</Button>
|
|
817
|
-
<Button variant="outline">Save</Button>
|
|
818
|
-
<Button variant="outline" size="icon">
|
|
819
|
-
<Icon symbol="arrow_forward" />
|
|
820
|
-
</Button>
|
|
821
|
-
<Button variant="outline">Delete</Button>
|
|
822
|
-
</ButtonGroup>;
|
|
823
|
-
```
|
|
824
|
-
|
|
825
|
-
## Notas de Implementación
|
|
826
|
-
|
|
827
|
-
- Basado en CVA (class-variance-authority) para variants
|
|
828
|
-
- Usa selectores CSS avanzados para bordes/esquinas automáticas
|
|
829
|
-
- Focus z-index (`focus-visible:z-10 focus-visible:relative`) previene overlap
|
|
830
|
-
- Width auto-ajustable con `w-fit`
|
|
831
|
-
- `items-stretch` estira items verticalmente para altura consistente
|
|
832
|
-
- Separadores usan componente Separator con `self-stretch`
|
|
833
|
-
- Grupos anidados detectados con `has-[>[data-slot=button-group]]` para aplicar gap
|
|
834
|
-
- Input en grupo usa `flex-1` para expandirse
|
|
835
|
-
- Select width ajustado con `[&>[data-slot=select-trigger]:not([class*=w-])]:w-fit`
|
|
836
|
-
- Role="group" para accesibilidad semántica
|
|
837
|
-
|
|
838
|
-
## Accesibilidad
|
|
839
|
-
|
|
840
|
-
### Role Group
|
|
841
|
-
|
|
842
|
-
```tsx
|
|
843
|
-
{
|
|
844
|
-
/* ✅ Automático - role="group" aplicado */
|
|
845
|
-
}
|
|
846
|
-
<ButtonGroup>
|
|
847
|
-
<Button variant="outline">One</Button>
|
|
848
|
-
<Button variant="outline">Two</Button>
|
|
849
|
-
</ButtonGroup>;
|
|
850
|
-
```
|
|
851
|
-
|
|
852
|
-
### aria-label para Grupos
|
|
853
|
-
|
|
854
|
-
```tsx
|
|
855
|
-
{
|
|
856
|
-
/* ✅ Correcto - Describe el propósito del grupo */
|
|
857
|
-
}
|
|
858
|
-
<ButtonGroup aria-label="Text alignment options">
|
|
859
|
-
<Button variant="outline" size="icon" aria-label="Align left">
|
|
860
|
-
<Icon symbol="format_align_left" />
|
|
861
|
-
</Button>
|
|
862
|
-
<Button variant="outline" size="icon" aria-label="Align center">
|
|
863
|
-
<Icon symbol="format_align_center" />
|
|
864
|
-
</Button>
|
|
865
|
-
<Button variant="outline" size="icon" aria-label="Align right">
|
|
866
|
-
<Icon symbol="format_align_right" />
|
|
867
|
-
</Button>
|
|
868
|
-
</ButtonGroup>;
|
|
869
|
-
```
|
|
870
|
-
|
|
871
|
-
### Navegación por Teclado
|
|
872
|
-
|
|
873
|
-
- ✅ **Tab**: Navega entre botones del grupo
|
|
874
|
-
- ✅ **Arrow keys**: No implementado por defecto (considera `role="toolbar"` si necesitas)
|
|
875
|
-
- ✅ **Space/Enter**: Activa el botón enfocado
|
|
876
|
-
|
|
877
|
-
### Estados Toggle
|
|
878
|
-
|
|
879
|
-
```tsx
|
|
880
|
-
{
|
|
881
|
-
/* ✅ Correcto - aria-pressed para toggles */
|
|
882
|
-
}
|
|
883
|
-
<ButtonGroup>
|
|
884
|
-
<Button
|
|
885
|
-
variant={isBold ? "default" : "outline"}
|
|
886
|
-
size="icon"
|
|
887
|
-
aria-pressed={isBold}
|
|
888
|
-
onClick={() => setIsBold(!isBold)}
|
|
889
|
-
>
|
|
890
|
-
<Icon symbol="format_bold" />
|
|
891
|
-
</Button>
|
|
892
|
-
</ButtonGroup>;
|
|
893
|
-
```
|
|
894
|
-
|
|
895
|
-
### Focus Visual
|
|
896
|
-
|
|
897
|
-
El focus ring se superpone correctamente gracias a `focus-visible:z-10 focus-visible:relative`.
|
|
898
|
-
|
|
899
|
-
## Troubleshooting
|
|
900
|
-
|
|
901
|
-
### Grupo Vertical Demasiado Alto
|
|
902
|
-
|
|
903
|
-
**Problema**: El grupo vertical se estira al 100% de altura.
|
|
904
|
-
|
|
905
|
-
**Solución**:
|
|
906
|
-
|
|
907
|
-
```tsx
|
|
908
|
-
// ❌ Problema - se estira
|
|
909
|
-
<ButtonGroup orientation="vertical">
|
|
910
|
-
<Button variant="outline">One</Button>
|
|
911
|
-
<Button variant="outline">Two</Button>
|
|
912
|
-
</ButtonGroup>
|
|
913
|
-
|
|
914
|
-
// ✅ Solución - agrega h-fit
|
|
915
|
-
<ButtonGroup orientation="vertical" className="h-fit">
|
|
916
|
-
<Button variant="outline">One</Button>
|
|
917
|
-
<Button variant="outline">Two</Button>
|
|
918
|
-
</ButtonGroup>
|
|
919
|
-
```
|
|
920
|
-
|
|
921
|
-
### Bordes Dobles Visibles
|
|
922
|
-
|
|
923
|
-
**Problema**: Se ven bordes dobles entre botones.
|
|
924
|
-
|
|
925
|
-
**Solución**:
|
|
926
|
-
|
|
927
|
-
```tsx
|
|
928
|
-
// ❌ Problema - variantes con bordes gruesos
|
|
929
|
-
<ButtonGroup>
|
|
930
|
-
<Button>One</Button> {/* variant="default" tiene border */}
|
|
931
|
-
<Button>Two</Button>
|
|
932
|
-
</ButtonGroup>
|
|
933
|
-
|
|
934
|
-
// ✅ Solución - usa outline o agrega separadores
|
|
935
|
-
<ButtonGroup>
|
|
936
|
-
<Button variant="outline">One</Button>
|
|
937
|
-
<Button variant="outline">Two</Button>
|
|
938
|
-
</ButtonGroup>
|
|
939
|
-
```
|
|
940
|
-
|
|
941
|
-
### Input No Se Expande
|
|
942
|
-
|
|
943
|
-
**Problema**: Input dentro del grupo no toma espacio disponible.
|
|
944
|
-
|
|
945
|
-
**Solución**:
|
|
946
|
-
|
|
947
|
-
```tsx
|
|
948
|
-
// ✅ El input automáticamente usa flex-1
|
|
949
|
-
<ButtonGroup>
|
|
950
|
-
<Input placeholder="Search..." /> {/* Se expande automáticamente */}
|
|
951
|
-
<Button variant="outline" size="icon">
|
|
952
|
-
<Icon symbol="search" />
|
|
953
|
-
</Button>
|
|
954
|
-
</ButtonGroup>
|
|
955
|
-
```
|
|
956
|
-
|
|
957
|
-
### Spacing entre Grupos Anidados
|
|
958
|
-
|
|
959
|
-
**Problema**: No hay espacio entre grupos anidados.
|
|
960
|
-
|
|
961
|
-
**Solución**:
|
|
962
|
-
|
|
963
|
-
```tsx
|
|
964
|
-
// ✅ El spacing se aplica automáticamente
|
|
965
|
-
<ButtonGroup>
|
|
966
|
-
<ButtonGroup>
|
|
967
|
-
<Button variant="outline">1</Button>
|
|
968
|
-
<Button variant="outline">2</Button>
|
|
969
|
-
</ButtonGroup>
|
|
970
|
-
<ButtonGroup>
|
|
971
|
-
{" "}
|
|
972
|
-
{/* gap-2 automático */}
|
|
973
|
-
<Button variant="outline">3</Button>
|
|
974
|
-
<Button variant="outline">4</Button>
|
|
975
|
-
</ButtonGroup>
|
|
976
|
-
</ButtonGroup>
|
|
977
|
-
```
|
|
978
|
-
|
|
979
|
-
## Referencias
|
|
980
|
-
|
|
981
|
-
- CVA (Class Variance Authority): https://cva.style/docs
|
|
982
|
-
- ARIA Group Role: https://www.w3.org/TR/wai-aria-1.2/#group
|
|
983
|
-
- ARIA Toolbar: https://www.w3.org/WAI/ARIA/apg/patterns/toolbar/
|
|
984
|
-
- Button Component: Ver documentación de Button
|
|
1
|
+
# Button Group Component
|
|
2
|
+
|
|
3
|
+
## Descripción
|
|
4
|
+
|
|
5
|
+
Componente para **agrupar botones relacionados visualmente**, creando una interfaz cohesiva donde múltiples acciones están conectadas. Soporta orientación horizontal y vertical, separadores visuales, anidamiento de grupos, integración con inputs/selects, texto descriptivo, y manejo automático de bordes/esquinas. Ideal para toolbars, controles de medios, formateo de texto y acciones relacionadas.
|
|
6
|
+
|
|
7
|
+
## Características
|
|
8
|
+
|
|
9
|
+
- ✅ 2 orientaciones: horizontal (default) y vertical
|
|
10
|
+
- ✅ Separadores visuales entre botones (ButtonGroupSeparator)
|
|
11
|
+
- ✅ Texto descriptivo dentro del grupo (ButtonGroupText)
|
|
12
|
+
- ✅ Soporte para grupos anidados con spacing automático
|
|
13
|
+
- ✅ Integración con Input y Select
|
|
14
|
+
- ✅ Bordes y esquinas automáticamente ajustados
|
|
15
|
+
- ✅ Focus z-index para overlapping correcto
|
|
16
|
+
- ✅ Split button pattern (acción + dropdown)
|
|
17
|
+
- ✅ Compatible con todos los tamaños de Button
|
|
18
|
+
- ✅ Role="group" para accesibilidad
|
|
19
|
+
- ✅ Width auto-ajustable (w-fit)
|
|
20
|
+
|
|
21
|
+
## Importación
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
import {
|
|
25
|
+
ButtonGroup,
|
|
26
|
+
ButtonGroupSeparator,
|
|
27
|
+
ButtonGroupText,
|
|
28
|
+
} from "@adamosuiteservices/ui/button-group";
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Uso Básico
|
|
32
|
+
|
|
33
|
+
### Grupo Horizontal Simple
|
|
34
|
+
|
|
35
|
+
```tsx
|
|
36
|
+
import { Button } from "@adamosuiteservices/ui/button";
|
|
37
|
+
|
|
38
|
+
<ButtonGroup>
|
|
39
|
+
<Button variant="outline">Archive</Button>
|
|
40
|
+
<Button variant="outline">Report</Button>
|
|
41
|
+
<Button variant="outline">Snooze</Button>
|
|
42
|
+
</ButtonGroup>;
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
**Comportamiento**: Los botones se conectan visualmente eliminando bordes internos y redondeando solo las esquinas externas.
|
|
46
|
+
|
|
47
|
+
### Con Separadores
|
|
48
|
+
|
|
49
|
+
```tsx
|
|
50
|
+
<ButtonGroup>
|
|
51
|
+
<Button variant="secondary" size="sm">
|
|
52
|
+
Copy
|
|
53
|
+
</Button>
|
|
54
|
+
<ButtonGroupSeparator />
|
|
55
|
+
<Button variant="secondary" size="sm">
|
|
56
|
+
Paste
|
|
57
|
+
</Button>
|
|
58
|
+
</ButtonGroup>
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**Uso**: Los separadores son útiles con variantes no-outline (secondary, default, etc.) para mejorar la separación visual.
|
|
62
|
+
|
|
63
|
+
### Orientación Vertical
|
|
64
|
+
|
|
65
|
+
```tsx
|
|
66
|
+
import { Icon } from "@adamosuiteservices/ui/icon";
|
|
67
|
+
|
|
68
|
+
<ButtonGroup orientation="vertical" className="h-fit">
|
|
69
|
+
<Button variant="outline" size="icon">
|
|
70
|
+
<Icon symbol="add" />
|
|
71
|
+
</Button>
|
|
72
|
+
<Button variant="outline" size="icon">
|
|
73
|
+
<Icon symbol="remove" />
|
|
74
|
+
</Button>
|
|
75
|
+
</ButtonGroup>;
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
**Nota**: Agrega `className="h-fit"` para evitar que el grupo se estire verticalmente.
|
|
79
|
+
|
|
80
|
+
## Componentes
|
|
81
|
+
|
|
82
|
+
### ButtonGroup (Root)
|
|
83
|
+
|
|
84
|
+
Contenedor principal que agrupa botones.
|
|
85
|
+
|
|
86
|
+
| Prop | Tipo | Default | Descripción |
|
|
87
|
+
| ----------- | -------------------------------- | -------------- | -------------------------------------------- |
|
|
88
|
+
| orientation | `"horizontal" \| "vertical"` | `"horizontal"` | Orientación del grupo |
|
|
89
|
+
| className | `string` | - | Clases CSS adicionales |
|
|
90
|
+
| children | `ReactNode` | - | Botones, separadores, texto, inputs, selects |
|
|
91
|
+
| ...props | `HTMLAttributes<HTMLDivElement>` | - | Props nativas de div |
|
|
92
|
+
|
|
93
|
+
**Atributos automáticos**:
|
|
94
|
+
|
|
95
|
+
- `role="group"`: Para accesibilidad
|
|
96
|
+
- `data-slot="button-group"`: Identificación interna
|
|
97
|
+
- `data-orientation`: Valor de orientation prop
|
|
98
|
+
|
|
99
|
+
**Estilos aplicados**:
|
|
100
|
+
|
|
101
|
+
- `flex`: Display flex
|
|
102
|
+
- `w-fit`: Ancho auto-ajustable
|
|
103
|
+
- `items-stretch`: Estira items verticalmente
|
|
104
|
+
- Bordes y esquinas ajustados automáticamente según orientation
|
|
105
|
+
|
|
106
|
+
### ButtonGroupSeparator
|
|
107
|
+
|
|
108
|
+
Línea separadora visual entre botones.
|
|
109
|
+
|
|
110
|
+
| Prop | Tipo | Default | Descripción |
|
|
111
|
+
| ----------- | ---------------------------- | ------------ | ------------------------------ |
|
|
112
|
+
| orientation | `"horizontal" \| "vertical"` | `"vertical"` | Orientación del separador |
|
|
113
|
+
| className | `string` | - | Clases CSS adicionales |
|
|
114
|
+
| ...props | Props de Separator | - | Props del componente Separator |
|
|
115
|
+
|
|
116
|
+
**Estilos aplicados**:
|
|
117
|
+
|
|
118
|
+
- `bg-input`: Color de fondo
|
|
119
|
+
- `relative`: Posicionamiento
|
|
120
|
+
- `!m-0`: Sin márgenes
|
|
121
|
+
- `self-stretch`: Se estira para llenar altura/ancho
|
|
122
|
+
- `h-auto` cuando orientation="vertical"
|
|
123
|
+
|
|
124
|
+
**Nota**: Usa `orientation="vertical"` (default) para grupos horizontales, y `orientation="horizontal"` para grupos verticales.
|
|
125
|
+
|
|
126
|
+
### ButtonGroupText
|
|
127
|
+
|
|
128
|
+
Texto descriptivo dentro del grupo.
|
|
129
|
+
|
|
130
|
+
| Prop | Tipo | Default | Descripción |
|
|
131
|
+
| --------- | -------------------------------- | ------- | --------------------------------- |
|
|
132
|
+
| asChild | `boolean` | `false` | Renderiza el hijo en lugar de div |
|
|
133
|
+
| className | `string` | - | Clases CSS adicionales |
|
|
134
|
+
| children | `ReactNode` | - | Texto o contenido |
|
|
135
|
+
| ...props | `HTMLAttributes<HTMLDivElement>` | - | Props nativas de div |
|
|
136
|
+
|
|
137
|
+
**Estilos aplicados**:
|
|
138
|
+
|
|
139
|
+
- `bg-muted`: Fondo gris claro
|
|
140
|
+
- `flex items-center gap-2`: Layout flex centrado
|
|
141
|
+
- `rounded-md border`: Bordes redondeados
|
|
142
|
+
- `px-4`: Padding horizontal
|
|
143
|
+
- `text-sm font-medium`: Tipografía
|
|
144
|
+
- `shadow-xs`: Sombra sutil
|
|
145
|
+
- Iconos auto-dimensionados a 16px
|
|
146
|
+
|
|
147
|
+
## Orientaciones
|
|
148
|
+
|
|
149
|
+
### horizontal (Default)
|
|
150
|
+
|
|
151
|
+
Botones en fila horizontal.
|
|
152
|
+
|
|
153
|
+
```tsx
|
|
154
|
+
<ButtonGroup orientation="horizontal">
|
|
155
|
+
<Button variant="outline">First</Button>
|
|
156
|
+
<Button variant="outline">Second</Button>
|
|
157
|
+
<Button variant="outline">Third</Button>
|
|
158
|
+
</ButtonGroup>
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
**Estilos automáticos**:
|
|
162
|
+
|
|
163
|
+
- `:not(:first-child)`: `rounded-l-none border-l-0` (elimina esquinas izquierdas y borde)
|
|
164
|
+
- `:not(:last-child)`: `rounded-r-none` (elimina esquinas derechas)
|
|
165
|
+
|
|
166
|
+
### vertical
|
|
167
|
+
|
|
168
|
+
Botones apilados verticalmente.
|
|
169
|
+
|
|
170
|
+
```tsx
|
|
171
|
+
<ButtonGroup orientation="vertical" className="h-fit">
|
|
172
|
+
<Button variant="outline">First</Button>
|
|
173
|
+
<Button variant="outline">Second</Button>
|
|
174
|
+
<Button variant="outline">Third</Button>
|
|
175
|
+
</ButtonGroup>
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
**Estilos automáticos**:
|
|
179
|
+
|
|
180
|
+
- `flex-col`: Dirección vertical
|
|
181
|
+
- `:not(:first-child)`: `rounded-t-none border-t-0` (elimina esquinas superiores y borde)
|
|
182
|
+
- `:not(:last-child)`: `rounded-b-none` (elimina esquinas inferiores)
|
|
183
|
+
|
|
184
|
+
## Tamaños
|
|
185
|
+
|
|
186
|
+
Todos los tamaños de Button funcionan en ButtonGroup:
|
|
187
|
+
|
|
188
|
+
```tsx
|
|
189
|
+
{
|
|
190
|
+
/* Small */
|
|
191
|
+
}
|
|
192
|
+
<ButtonGroup>
|
|
193
|
+
<Button variant="outline" size="sm">
|
|
194
|
+
Small
|
|
195
|
+
</Button>
|
|
196
|
+
<Button variant="outline" size="sm">
|
|
197
|
+
Buttons
|
|
198
|
+
</Button>
|
|
199
|
+
<Button variant="outline" size="icon-sm">
|
|
200
|
+
<Icon symbol="add" />
|
|
201
|
+
</Button>
|
|
202
|
+
</ButtonGroup>;
|
|
203
|
+
|
|
204
|
+
{
|
|
205
|
+
/* Default */
|
|
206
|
+
}
|
|
207
|
+
<ButtonGroup>
|
|
208
|
+
<Button variant="outline">Default</Button>
|
|
209
|
+
<Button variant="outline">Buttons</Button>
|
|
210
|
+
<Button variant="outline" size="icon">
|
|
211
|
+
<Icon symbol="add" />
|
|
212
|
+
</Button>
|
|
213
|
+
</ButtonGroup>;
|
|
214
|
+
|
|
215
|
+
{
|
|
216
|
+
/* Large */
|
|
217
|
+
}
|
|
218
|
+
<ButtonGroup>
|
|
219
|
+
<Button variant="outline" size="lg">
|
|
220
|
+
Large
|
|
221
|
+
</Button>
|
|
222
|
+
<Button variant="outline" size="lg">
|
|
223
|
+
Buttons
|
|
224
|
+
</Button>
|
|
225
|
+
<Button variant="outline" size="icon-lg">
|
|
226
|
+
<Icon symbol="add" />
|
|
227
|
+
</Button>
|
|
228
|
+
</ButtonGroup>;
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
**Regla importante**: Mantén tamaños consistentes dentro del mismo grupo.
|
|
232
|
+
|
|
233
|
+
## Patrones Avanzados
|
|
234
|
+
|
|
235
|
+
### Grupos Anidados
|
|
236
|
+
|
|
237
|
+
Para crear toolbars complejas con espaciado entre grupos lógicos.
|
|
238
|
+
|
|
239
|
+
```tsx
|
|
240
|
+
<ButtonGroup>
|
|
241
|
+
<ButtonGroup>
|
|
242
|
+
<Button variant="outline" size="sm">
|
|
243
|
+
1
|
|
244
|
+
</Button>
|
|
245
|
+
<Button variant="outline" size="sm">
|
|
246
|
+
2
|
|
247
|
+
</Button>
|
|
248
|
+
<Button variant="outline" size="sm">
|
|
249
|
+
3
|
|
250
|
+
</Button>
|
|
251
|
+
</ButtonGroup>
|
|
252
|
+
<ButtonGroup>
|
|
253
|
+
<Button variant="outline" size="icon-sm">
|
|
254
|
+
<Icon symbol="arrow_back" />
|
|
255
|
+
</Button>
|
|
256
|
+
<Button variant="outline" size="icon-sm">
|
|
257
|
+
<Icon symbol="arrow_forward" />
|
|
258
|
+
</Button>
|
|
259
|
+
</ButtonGroup>
|
|
260
|
+
</ButtonGroup>
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
**Comportamiento**: Los grupos anidados tienen `gap-2` automático entre ellos.
|
|
264
|
+
|
|
265
|
+
### Split Button
|
|
266
|
+
|
|
267
|
+
Botón con acción principal y menú dropdown.
|
|
268
|
+
|
|
269
|
+
```tsx
|
|
270
|
+
<ButtonGroup>
|
|
271
|
+
<Button variant="secondary">Deploy</Button>
|
|
272
|
+
<ButtonGroupSeparator />
|
|
273
|
+
<Button size="icon" variant="secondary">
|
|
274
|
+
<Icon symbol="keyboard_arrow_down" />
|
|
275
|
+
</Button>
|
|
276
|
+
</ButtonGroup>
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
**Uso común**: Acción por defecto + opciones adicionales en dropdown.
|
|
280
|
+
|
|
281
|
+
### Con Input (Search Box)
|
|
282
|
+
|
|
283
|
+
```tsx
|
|
284
|
+
import { Input } from "@adamosuiteservices/ui/input";
|
|
285
|
+
import { Icon } from "@adamosuiteservices/ui/icon";
|
|
286
|
+
|
|
287
|
+
<ButtonGroup>
|
|
288
|
+
<Input placeholder="Search..." />
|
|
289
|
+
<Button variant="outline" aria-label="Search">
|
|
290
|
+
<Icon symbol="search" />
|
|
291
|
+
</Button>
|
|
292
|
+
</ButtonGroup>;
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
**Comportamiento**: El input se integra visualmente con el botón, creando una barra de búsqueda cohesiva.
|
|
296
|
+
|
|
297
|
+
### Con Select (Currency Selector)
|
|
298
|
+
|
|
299
|
+
```tsx
|
|
300
|
+
import { Input } from "@adamosuiteservices/ui/input";
|
|
301
|
+
import {
|
|
302
|
+
Select,
|
|
303
|
+
SelectTrigger,
|
|
304
|
+
SelectContent,
|
|
305
|
+
SelectItem,
|
|
306
|
+
} from "@adamosuiteservices/ui/select";
|
|
307
|
+
import { Icon } from "@adamosuiteservices/ui/icon";
|
|
308
|
+
|
|
309
|
+
function CurrencyInput() {
|
|
310
|
+
const [currency, setCurrency] = useState("$");
|
|
311
|
+
|
|
312
|
+
return (
|
|
313
|
+
<ButtonGroup>
|
|
314
|
+
<ButtonGroup>
|
|
315
|
+
<Select value={currency} onValueChange={setCurrency}>
|
|
316
|
+
<SelectTrigger className="font-mono">{currency}</SelectTrigger>
|
|
317
|
+
<SelectContent className="min-w-24">
|
|
318
|
+
<SelectItem value="$">$ US Dollar</SelectItem>
|
|
319
|
+
<SelectItem value="€">€ Euro</SelectItem>
|
|
320
|
+
<SelectItem value="£">£ British Pound</SelectItem>
|
|
321
|
+
</SelectContent>
|
|
322
|
+
</Select>
|
|
323
|
+
<Input placeholder="10.00" pattern="[0-9]*" />
|
|
324
|
+
</ButtonGroup>
|
|
325
|
+
<ButtonGroup>
|
|
326
|
+
<Button aria-label="Send" size="icon" variant="outline">
|
|
327
|
+
<Icon symbol="arrow_forward" />
|
|
328
|
+
</Button>
|
|
329
|
+
</ButtonGroup>
|
|
330
|
+
</ButtonGroup>
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
### Controles de Medios
|
|
336
|
+
|
|
337
|
+
Player de audio/video con controles agrupados.
|
|
338
|
+
|
|
339
|
+
```tsx
|
|
340
|
+
import { Icon } from "@adamosuiteservices/ui/icon";
|
|
341
|
+
|
|
342
|
+
function MediaControls() {
|
|
343
|
+
const [isPlaying, setIsPlaying] = useState(false);
|
|
344
|
+
|
|
345
|
+
return (
|
|
346
|
+
<div className="space-y-4">
|
|
347
|
+
{/* Controles de reproducción */}
|
|
348
|
+
<ButtonGroup>
|
|
349
|
+
<Button variant="outline" size="icon">
|
|
350
|
+
<Icon symbol="skip_previous" />
|
|
351
|
+
</Button>
|
|
352
|
+
<Button
|
|
353
|
+
variant="outline"
|
|
354
|
+
size="icon"
|
|
355
|
+
onClick={() => setIsPlaying(!isPlaying)}
|
|
356
|
+
>
|
|
357
|
+
{isPlaying ? <Icon symbol="pause" /> : <Icon symbol="play_arrow" />}
|
|
358
|
+
</Button>
|
|
359
|
+
<Button variant="outline" size="icon">
|
|
360
|
+
<Icon symbol="stop" />
|
|
361
|
+
</Button>
|
|
362
|
+
<Button variant="outline" size="icon">
|
|
363
|
+
<Icon symbol="skip_next" />
|
|
364
|
+
</Button>
|
|
365
|
+
</ButtonGroup>
|
|
366
|
+
|
|
367
|
+
{/* Controles de volumen */}
|
|
368
|
+
<ButtonGroup>
|
|
369
|
+
<Button variant="outline" size="icon">
|
|
370
|
+
<Icon symbol="volume_off" />
|
|
371
|
+
</Button>
|
|
372
|
+
<ButtonGroupText className="px-4">Volume</ButtonGroupText>
|
|
373
|
+
<Button variant="outline" size="icon">
|
|
374
|
+
<Icon symbol="volume_up" />
|
|
375
|
+
</Button>
|
|
376
|
+
</ButtonGroup>
|
|
377
|
+
</div>
|
|
378
|
+
);
|
|
379
|
+
}
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
### Editor de Texto (Toolbar)
|
|
383
|
+
|
|
384
|
+
Controles de formateo con estados toggle.
|
|
385
|
+
|
|
386
|
+
```tsx
|
|
387
|
+
import { Icon } from "@adamosuiteservices/ui/icon";
|
|
388
|
+
|
|
389
|
+
function TextEditor() {
|
|
390
|
+
const [formatting, setFormatting] = useState({
|
|
391
|
+
bold: false,
|
|
392
|
+
italic: false,
|
|
393
|
+
underline: false,
|
|
394
|
+
});
|
|
395
|
+
const [alignment, setAlignment] = useState("left");
|
|
396
|
+
|
|
397
|
+
return (
|
|
398
|
+
<div className="space-y-4">
|
|
399
|
+
{/* Formato de texto */}
|
|
400
|
+
<ButtonGroup>
|
|
401
|
+
<Button
|
|
402
|
+
variant={formatting.bold ? "default" : "outline"}
|
|
403
|
+
size="icon"
|
|
404
|
+
onClick={() =>
|
|
405
|
+
setFormatting((prev) => ({ ...prev, bold: !prev.bold }))
|
|
406
|
+
}
|
|
407
|
+
>
|
|
408
|
+
<Icon symbol="format_bold" />
|
|
409
|
+
</Button>
|
|
410
|
+
<Button
|
|
411
|
+
variant={formatting.italic ? "default" : "outline"}
|
|
412
|
+
size="icon"
|
|
413
|
+
onClick={() =>
|
|
414
|
+
setFormatting((prev) => ({ ...prev, italic: !prev.italic }))
|
|
415
|
+
}
|
|
416
|
+
>
|
|
417
|
+
<Icon symbol="format_italic" />
|
|
418
|
+
</Button>
|
|
419
|
+
<Button
|
|
420
|
+
variant={formatting.underline ? "default" : "outline"}
|
|
421
|
+
size="icon"
|
|
422
|
+
onClick={() =>
|
|
423
|
+
setFormatting((prev) => ({ ...prev, underline: !prev.underline }))
|
|
424
|
+
}
|
|
425
|
+
>
|
|
426
|
+
<Icon symbol="format_underlined" />
|
|
427
|
+
</Button>
|
|
428
|
+
</ButtonGroup>
|
|
429
|
+
|
|
430
|
+
{/* Alineación */}
|
|
431
|
+
<ButtonGroup>
|
|
432
|
+
<Button
|
|
433
|
+
variant={alignment === "left" ? "default" : "outline"}
|
|
434
|
+
size="icon"
|
|
435
|
+
onClick={() => setAlignment("left")}
|
|
436
|
+
>
|
|
437
|
+
<Icon symbol="format_align_left" />
|
|
438
|
+
</Button>
|
|
439
|
+
<Button
|
|
440
|
+
variant={alignment === "center" ? "default" : "outline"}
|
|
441
|
+
size="icon"
|
|
442
|
+
onClick={() => setAlignment("center")}
|
|
443
|
+
>
|
|
444
|
+
<Icon symbol="format_align_center" />
|
|
445
|
+
</Button>
|
|
446
|
+
<Button
|
|
447
|
+
variant={alignment === "right" ? "default" : "outline"}
|
|
448
|
+
size="icon"
|
|
449
|
+
onClick={() => setAlignment("right")}
|
|
450
|
+
>
|
|
451
|
+
<Icon symbol="format_align_right" />
|
|
452
|
+
</Button>
|
|
453
|
+
</ButtonGroup>
|
|
454
|
+
</div>
|
|
455
|
+
);
|
|
456
|
+
}
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
### Acciones de Archivo
|
|
460
|
+
|
|
461
|
+
Grupos para operaciones de archivo.
|
|
462
|
+
|
|
463
|
+
```tsx
|
|
464
|
+
import { Icon } from "@adamosuiteservices/ui/icon";
|
|
465
|
+
|
|
466
|
+
<div className="space-y-4">
|
|
467
|
+
{/* Acciones principales */}
|
|
468
|
+
<ButtonGroup>
|
|
469
|
+
<Button variant="outline">
|
|
470
|
+
<Icon symbol="save" />
|
|
471
|
+
Save
|
|
472
|
+
</Button>
|
|
473
|
+
<Button variant="outline">
|
|
474
|
+
<Icon symbol="download" />
|
|
475
|
+
Download
|
|
476
|
+
</Button>
|
|
477
|
+
<Button variant="outline">
|
|
478
|
+
<Icon symbol="upload" />
|
|
479
|
+
Upload
|
|
480
|
+
</Button>
|
|
481
|
+
</ButtonGroup>
|
|
482
|
+
|
|
483
|
+
{/* Acciones rápidas */}
|
|
484
|
+
<ButtonGroup>
|
|
485
|
+
<Button variant="outline" size="icon">
|
|
486
|
+
<Icon symbol="undo" />
|
|
487
|
+
</Button>
|
|
488
|
+
<Button variant="outline" size="icon">
|
|
489
|
+
<Icon symbol="redo" />
|
|
490
|
+
</Button>
|
|
491
|
+
<ButtonGroupSeparator />
|
|
492
|
+
<Button variant="outline" size="icon">
|
|
493
|
+
<Icon symbol="content_copy" />
|
|
494
|
+
</Button>
|
|
495
|
+
<Button variant="outline" size="icon">
|
|
496
|
+
<Icon symbol="share" />
|
|
497
|
+
</Button>
|
|
498
|
+
<ButtonGroupSeparator />
|
|
499
|
+
<Button variant="outline" size="icon">
|
|
500
|
+
<Icon symbol="delete" />
|
|
501
|
+
</Button>
|
|
502
|
+
</ButtonGroup>
|
|
503
|
+
</div>;
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
### Toolbar Compleja
|
|
507
|
+
|
|
508
|
+
Layout con múltiples grupos anidados.
|
|
509
|
+
|
|
510
|
+
```tsx
|
|
511
|
+
import { Icon } from "@adamosuiteservices/ui/icon";
|
|
512
|
+
|
|
513
|
+
<ButtonGroup>
|
|
514
|
+
<ButtonGroup>
|
|
515
|
+
<Button variant="outline" size="icon">
|
|
516
|
+
<Icon symbol="arrow_back" />
|
|
517
|
+
</Button>
|
|
518
|
+
</ButtonGroup>
|
|
519
|
+
<ButtonGroup>
|
|
520
|
+
<Button variant="outline">Archive</Button>
|
|
521
|
+
<Button variant="outline">Report</Button>
|
|
522
|
+
</ButtonGroup>
|
|
523
|
+
<ButtonGroup>
|
|
524
|
+
<Button variant="outline">Snooze</Button>
|
|
525
|
+
<Button variant="outline" size="icon">
|
|
526
|
+
<Icon symbol="more_horiz" />
|
|
527
|
+
</Button>
|
|
528
|
+
</ButtonGroup>
|
|
529
|
+
<ButtonGroup>
|
|
530
|
+
<Button variant="outline" size="icon">
|
|
531
|
+
<Icon symbol="settings" />
|
|
532
|
+
</Button>
|
|
533
|
+
<Button variant="outline" size="icon">
|
|
534
|
+
<Icon symbol="refresh" />
|
|
535
|
+
</Button>
|
|
536
|
+
</ButtonGroup>
|
|
537
|
+
</ButtonGroup>;
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
### Con Texto Descriptivo
|
|
541
|
+
|
|
542
|
+
Etiquetas para claridad contextual.
|
|
543
|
+
|
|
544
|
+
```tsx
|
|
545
|
+
import { Icon } from "@adamosuiteservices/ui/icon";
|
|
546
|
+
|
|
547
|
+
<div className="space-y-4">
|
|
548
|
+
{/* Texto antes */}
|
|
549
|
+
<ButtonGroup>
|
|
550
|
+
<ButtonGroupText>Actions:</ButtonGroupText>
|
|
551
|
+
<Button variant="outline">Edit</Button>
|
|
552
|
+
<Button variant="outline">Delete</Button>
|
|
553
|
+
</ButtonGroup>
|
|
554
|
+
|
|
555
|
+
{/* Texto entre botones */}
|
|
556
|
+
<ButtonGroup>
|
|
557
|
+
<Button variant="outline">
|
|
558
|
+
<Icon symbol="edit" />
|
|
559
|
+
Edit
|
|
560
|
+
</Button>
|
|
561
|
+
<ButtonGroupText>or</ButtonGroupText>
|
|
562
|
+
<Button variant="outline">
|
|
563
|
+
<Icon symbol="content_copy" />
|
|
564
|
+
Copy
|
|
565
|
+
</Button>
|
|
566
|
+
</ButtonGroup>
|
|
567
|
+
</div>;
|
|
568
|
+
```
|
|
569
|
+
|
|
570
|
+
## Casos de Uso Comunes
|
|
571
|
+
|
|
572
|
+
### Paginación
|
|
573
|
+
|
|
574
|
+
```tsx
|
|
575
|
+
import { Icon } from "@adamosuiteservices/ui/icon";
|
|
576
|
+
|
|
577
|
+
<ButtonGroup>
|
|
578
|
+
<Button variant="outline" size="icon" disabled={currentPage === 1}>
|
|
579
|
+
<Icon symbol="chevron_left" />
|
|
580
|
+
</Button>
|
|
581
|
+
<ButtonGroupText>
|
|
582
|
+
{currentPage} of {totalPages}
|
|
583
|
+
</ButtonGroupText>
|
|
584
|
+
<Button variant="outline" size="icon" disabled={currentPage === totalPages}>
|
|
585
|
+
<Icon symbol="chevron_right" />
|
|
586
|
+
</Button>
|
|
587
|
+
</ButtonGroup>;
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
### Filtros
|
|
591
|
+
|
|
592
|
+
```tsx
|
|
593
|
+
<ButtonGroup>
|
|
594
|
+
<ButtonGroupText>Filter:</ButtonGroupText>
|
|
595
|
+
<Button
|
|
596
|
+
variant={filter === "all" ? "default" : "outline"}
|
|
597
|
+
onClick={() => setFilter("all")}
|
|
598
|
+
>
|
|
599
|
+
All
|
|
600
|
+
</Button>
|
|
601
|
+
<Button
|
|
602
|
+
variant={filter === "active" ? "default" : "outline"}
|
|
603
|
+
onClick={() => setFilter("active")}
|
|
604
|
+
>
|
|
605
|
+
Active
|
|
606
|
+
</Button>
|
|
607
|
+
<Button
|
|
608
|
+
variant={filter === "archived" ? "default" : "outline"}
|
|
609
|
+
onClick={() => setFilter("archived")}
|
|
610
|
+
>
|
|
611
|
+
Archived
|
|
612
|
+
</Button>
|
|
613
|
+
</ButtonGroup>
|
|
614
|
+
```
|
|
615
|
+
|
|
616
|
+
### Tabs Alternativos
|
|
617
|
+
|
|
618
|
+
```tsx
|
|
619
|
+
<ButtonGroup>
|
|
620
|
+
<Button
|
|
621
|
+
variant={tab === "overview" ? "default" : "outline"}
|
|
622
|
+
onClick={() => setTab("overview")}
|
|
623
|
+
>
|
|
624
|
+
Overview
|
|
625
|
+
</Button>
|
|
626
|
+
<Button
|
|
627
|
+
variant={tab === "analytics" ? "default" : "outline"}
|
|
628
|
+
onClick={() => setTab("analytics")}
|
|
629
|
+
>
|
|
630
|
+
Analytics
|
|
631
|
+
</Button>
|
|
632
|
+
<Button
|
|
633
|
+
variant={tab === "reports" ? "default" : "outline"}
|
|
634
|
+
onClick={() => setTab("reports")}
|
|
635
|
+
>
|
|
636
|
+
Reports
|
|
637
|
+
</Button>
|
|
638
|
+
</ButtonGroup>
|
|
639
|
+
```
|
|
640
|
+
|
|
641
|
+
### Vista Toggle (List/Grid)
|
|
642
|
+
|
|
643
|
+
```tsx
|
|
644
|
+
import { Icon } from "@adamosuiteservices/ui/icon";
|
|
645
|
+
|
|
646
|
+
<ButtonGroup>
|
|
647
|
+
<Button
|
|
648
|
+
variant={view === "grid" ? "default" : "outline"}
|
|
649
|
+
size="icon"
|
|
650
|
+
onClick={() => setView("grid")}
|
|
651
|
+
aria-label="Grid view"
|
|
652
|
+
>
|
|
653
|
+
<Icon symbol="grid_view" />
|
|
654
|
+
</Button>
|
|
655
|
+
<Button
|
|
656
|
+
variant={view === "list" ? "default" : "outline"}
|
|
657
|
+
size="icon"
|
|
658
|
+
onClick={() => setView("list")}
|
|
659
|
+
aria-label="List view"
|
|
660
|
+
>
|
|
661
|
+
<Icon symbol="list" />
|
|
662
|
+
</Button>
|
|
663
|
+
</ButtonGroup>;
|
|
664
|
+
```
|
|
665
|
+
|
|
666
|
+
### Zoom Controls
|
|
667
|
+
|
|
668
|
+
```tsx
|
|
669
|
+
import { Icon } from "@adamosuiteservices/ui/icon";
|
|
670
|
+
|
|
671
|
+
<ButtonGroup>
|
|
672
|
+
<Button variant="outline" size="icon" onClick={handleZoomOut}>
|
|
673
|
+
<Icon symbol="zoom_out" />
|
|
674
|
+
</Button>
|
|
675
|
+
<ButtonGroupText className="px-6 font-mono">{zoom}%</ButtonGroupText>
|
|
676
|
+
<Button variant="outline" size="icon" onClick={handleZoomIn}>
|
|
677
|
+
<Icon symbol="zoom_in" />
|
|
678
|
+
</Button>
|
|
679
|
+
</ButtonGroup>;
|
|
680
|
+
```
|
|
681
|
+
|
|
682
|
+
## Mejores Prácticas
|
|
683
|
+
|
|
684
|
+
### Consistencia de Tamaños
|
|
685
|
+
|
|
686
|
+
```tsx
|
|
687
|
+
{
|
|
688
|
+
/* ✅ Correcto - Tamaños consistentes */
|
|
689
|
+
}
|
|
690
|
+
<ButtonGroup>
|
|
691
|
+
<Button variant="outline" size="sm">
|
|
692
|
+
One
|
|
693
|
+
</Button>
|
|
694
|
+
<Button variant="outline" size="sm">
|
|
695
|
+
Two
|
|
696
|
+
</Button>
|
|
697
|
+
<Button variant="outline" size="icon-sm">
|
|
698
|
+
<Icon symbol="add" />
|
|
699
|
+
</Button>
|
|
700
|
+
</ButtonGroup>;
|
|
701
|
+
|
|
702
|
+
{
|
|
703
|
+
/* ❌ Incorrecto - Mezcla de tamaños */
|
|
704
|
+
}
|
|
705
|
+
<ButtonGroup>
|
|
706
|
+
<Button variant="outline" size="sm">
|
|
707
|
+
One
|
|
708
|
+
</Button>
|
|
709
|
+
<Button variant="outline">Two</Button> {/* Tamaño diferente */}
|
|
710
|
+
<Button variant="outline" size="lg">
|
|
711
|
+
Three
|
|
712
|
+
</Button>
|
|
713
|
+
</ButtonGroup>;
|
|
714
|
+
```
|
|
715
|
+
|
|
716
|
+
### Variantes Consistentes
|
|
717
|
+
|
|
718
|
+
```tsx
|
|
719
|
+
{
|
|
720
|
+
/* ✅ Correcto - Misma variante */
|
|
721
|
+
}
|
|
722
|
+
<ButtonGroup>
|
|
723
|
+
<Button variant="outline">One</Button>
|
|
724
|
+
<Button variant="outline">Two</Button>
|
|
725
|
+
<Button variant="outline">Three</Button>
|
|
726
|
+
</ButtonGroup>;
|
|
727
|
+
|
|
728
|
+
{
|
|
729
|
+
/* ⚠️ Permitido - Variantes diferentes para estado activo */
|
|
730
|
+
}
|
|
731
|
+
<ButtonGroup>
|
|
732
|
+
<Button variant={isActive ? "default" : "outline"}>Active</Button>
|
|
733
|
+
<Button variant="outline">Inactive</Button>
|
|
734
|
+
</ButtonGroup>;
|
|
735
|
+
```
|
|
736
|
+
|
|
737
|
+
### Separadores con Variantes No-Outline
|
|
738
|
+
|
|
739
|
+
```tsx
|
|
740
|
+
{
|
|
741
|
+
/* ✅ Correcto - Separador con variante secondary */
|
|
742
|
+
}
|
|
743
|
+
<ButtonGroup>
|
|
744
|
+
<Button variant="secondary">Copy</Button>
|
|
745
|
+
<ButtonGroupSeparator />
|
|
746
|
+
<Button variant="secondary">Paste</Button>
|
|
747
|
+
</ButtonGroup>;
|
|
748
|
+
|
|
749
|
+
{
|
|
750
|
+
/* ⚠️ Innecesario - Separador con outline (ya hay bordes) */
|
|
751
|
+
}
|
|
752
|
+
<ButtonGroup>
|
|
753
|
+
<Button variant="outline">Copy</Button>
|
|
754
|
+
<ButtonGroupSeparator /> {/* No se ve bien */}
|
|
755
|
+
<Button variant="outline">Paste</Button>
|
|
756
|
+
</ButtonGroup>;
|
|
757
|
+
```
|
|
758
|
+
|
|
759
|
+
### aria-label en Botones de Icono
|
|
760
|
+
|
|
761
|
+
```tsx
|
|
762
|
+
{
|
|
763
|
+
/* ✅ Correcto - Labels descriptivas */
|
|
764
|
+
}
|
|
765
|
+
<ButtonGroup>
|
|
766
|
+
<Button variant="outline" size="icon" aria-label="Previous page">
|
|
767
|
+
<Icon symbol="arrow_back" />
|
|
768
|
+
</Button>
|
|
769
|
+
<Button variant="outline" size="icon" aria-label="Next page">
|
|
770
|
+
<Icon symbol="arrow_forward" />
|
|
771
|
+
</Button>
|
|
772
|
+
</ButtonGroup>;
|
|
773
|
+
|
|
774
|
+
{
|
|
775
|
+
/* ❌ Incorrecto - Sin labels */
|
|
776
|
+
}
|
|
777
|
+
<ButtonGroup>
|
|
778
|
+
<Button variant="outline" size="icon">
|
|
779
|
+
<Icon symbol="arrow_back" />
|
|
780
|
+
</Button>
|
|
781
|
+
<Button variant="outline" size="icon">
|
|
782
|
+
<Icon symbol="arrow_forward" />
|
|
783
|
+
</Button>
|
|
784
|
+
</ButtonGroup>;
|
|
785
|
+
```
|
|
786
|
+
|
|
787
|
+
### Grupos Anidados para Organización
|
|
788
|
+
|
|
789
|
+
```tsx
|
|
790
|
+
{
|
|
791
|
+
/* ✅ Correcto - Agrupación lógica */
|
|
792
|
+
}
|
|
793
|
+
<ButtonGroup>
|
|
794
|
+
<ButtonGroup>
|
|
795
|
+
{/* Acciones de navegación */}
|
|
796
|
+
<Button variant="outline" size="icon">
|
|
797
|
+
<Icon symbol="arrow_back" />
|
|
798
|
+
</Button>
|
|
799
|
+
<Button variant="outline" size="icon">
|
|
800
|
+
<Icon symbol="arrow_forward" />
|
|
801
|
+
</Button>
|
|
802
|
+
</ButtonGroup>
|
|
803
|
+
<ButtonGroup>
|
|
804
|
+
{/* Acciones de contenido */}
|
|
805
|
+
<Button variant="outline">Save</Button>
|
|
806
|
+
<Button variant="outline">Delete</Button>
|
|
807
|
+
</ButtonGroup>
|
|
808
|
+
</ButtonGroup>;
|
|
809
|
+
|
|
810
|
+
{
|
|
811
|
+
/* ❌ Incorrecto - Todo mezclado */
|
|
812
|
+
}
|
|
813
|
+
<ButtonGroup>
|
|
814
|
+
<Button variant="outline" size="icon">
|
|
815
|
+
<Icon symbol="arrow_back" />
|
|
816
|
+
</Button>
|
|
817
|
+
<Button variant="outline">Save</Button>
|
|
818
|
+
<Button variant="outline" size="icon">
|
|
819
|
+
<Icon symbol="arrow_forward" />
|
|
820
|
+
</Button>
|
|
821
|
+
<Button variant="outline">Delete</Button>
|
|
822
|
+
</ButtonGroup>;
|
|
823
|
+
```
|
|
824
|
+
|
|
825
|
+
## Notas de Implementación
|
|
826
|
+
|
|
827
|
+
- Basado en CVA (class-variance-authority) para variants
|
|
828
|
+
- Usa selectores CSS avanzados para bordes/esquinas automáticas
|
|
829
|
+
- Focus z-index (`focus-visible:z-10 focus-visible:relative`) previene overlap
|
|
830
|
+
- Width auto-ajustable con `w-fit`
|
|
831
|
+
- `items-stretch` estira items verticalmente para altura consistente
|
|
832
|
+
- Separadores usan componente Separator con `self-stretch`
|
|
833
|
+
- Grupos anidados detectados con `has-[>[data-slot=button-group]]` para aplicar gap
|
|
834
|
+
- Input en grupo usa `flex-1` para expandirse
|
|
835
|
+
- Select width ajustado con `[&>[data-slot=select-trigger]:not([class*=w-])]:w-fit`
|
|
836
|
+
- Role="group" para accesibilidad semántica
|
|
837
|
+
|
|
838
|
+
## Accesibilidad
|
|
839
|
+
|
|
840
|
+
### Role Group
|
|
841
|
+
|
|
842
|
+
```tsx
|
|
843
|
+
{
|
|
844
|
+
/* ✅ Automático - role="group" aplicado */
|
|
845
|
+
}
|
|
846
|
+
<ButtonGroup>
|
|
847
|
+
<Button variant="outline">One</Button>
|
|
848
|
+
<Button variant="outline">Two</Button>
|
|
849
|
+
</ButtonGroup>;
|
|
850
|
+
```
|
|
851
|
+
|
|
852
|
+
### aria-label para Grupos
|
|
853
|
+
|
|
854
|
+
```tsx
|
|
855
|
+
{
|
|
856
|
+
/* ✅ Correcto - Describe el propósito del grupo */
|
|
857
|
+
}
|
|
858
|
+
<ButtonGroup aria-label="Text alignment options">
|
|
859
|
+
<Button variant="outline" size="icon" aria-label="Align left">
|
|
860
|
+
<Icon symbol="format_align_left" />
|
|
861
|
+
</Button>
|
|
862
|
+
<Button variant="outline" size="icon" aria-label="Align center">
|
|
863
|
+
<Icon symbol="format_align_center" />
|
|
864
|
+
</Button>
|
|
865
|
+
<Button variant="outline" size="icon" aria-label="Align right">
|
|
866
|
+
<Icon symbol="format_align_right" />
|
|
867
|
+
</Button>
|
|
868
|
+
</ButtonGroup>;
|
|
869
|
+
```
|
|
870
|
+
|
|
871
|
+
### Navegación por Teclado
|
|
872
|
+
|
|
873
|
+
- ✅ **Tab**: Navega entre botones del grupo
|
|
874
|
+
- ✅ **Arrow keys**: No implementado por defecto (considera `role="toolbar"` si necesitas)
|
|
875
|
+
- ✅ **Space/Enter**: Activa el botón enfocado
|
|
876
|
+
|
|
877
|
+
### Estados Toggle
|
|
878
|
+
|
|
879
|
+
```tsx
|
|
880
|
+
{
|
|
881
|
+
/* ✅ Correcto - aria-pressed para toggles */
|
|
882
|
+
}
|
|
883
|
+
<ButtonGroup>
|
|
884
|
+
<Button
|
|
885
|
+
variant={isBold ? "default" : "outline"}
|
|
886
|
+
size="icon"
|
|
887
|
+
aria-pressed={isBold}
|
|
888
|
+
onClick={() => setIsBold(!isBold)}
|
|
889
|
+
>
|
|
890
|
+
<Icon symbol="format_bold" />
|
|
891
|
+
</Button>
|
|
892
|
+
</ButtonGroup>;
|
|
893
|
+
```
|
|
894
|
+
|
|
895
|
+
### Focus Visual
|
|
896
|
+
|
|
897
|
+
El focus ring se superpone correctamente gracias a `focus-visible:z-10 focus-visible:relative`.
|
|
898
|
+
|
|
899
|
+
## Troubleshooting
|
|
900
|
+
|
|
901
|
+
### Grupo Vertical Demasiado Alto
|
|
902
|
+
|
|
903
|
+
**Problema**: El grupo vertical se estira al 100% de altura.
|
|
904
|
+
|
|
905
|
+
**Solución**:
|
|
906
|
+
|
|
907
|
+
```tsx
|
|
908
|
+
// ❌ Problema - se estira
|
|
909
|
+
<ButtonGroup orientation="vertical">
|
|
910
|
+
<Button variant="outline">One</Button>
|
|
911
|
+
<Button variant="outline">Two</Button>
|
|
912
|
+
</ButtonGroup>
|
|
913
|
+
|
|
914
|
+
// ✅ Solución - agrega h-fit
|
|
915
|
+
<ButtonGroup orientation="vertical" className="h-fit">
|
|
916
|
+
<Button variant="outline">One</Button>
|
|
917
|
+
<Button variant="outline">Two</Button>
|
|
918
|
+
</ButtonGroup>
|
|
919
|
+
```
|
|
920
|
+
|
|
921
|
+
### Bordes Dobles Visibles
|
|
922
|
+
|
|
923
|
+
**Problema**: Se ven bordes dobles entre botones.
|
|
924
|
+
|
|
925
|
+
**Solución**:
|
|
926
|
+
|
|
927
|
+
```tsx
|
|
928
|
+
// ❌ Problema - variantes con bordes gruesos
|
|
929
|
+
<ButtonGroup>
|
|
930
|
+
<Button>One</Button> {/* variant="default" tiene border */}
|
|
931
|
+
<Button>Two</Button>
|
|
932
|
+
</ButtonGroup>
|
|
933
|
+
|
|
934
|
+
// ✅ Solución - usa outline o agrega separadores
|
|
935
|
+
<ButtonGroup>
|
|
936
|
+
<Button variant="outline">One</Button>
|
|
937
|
+
<Button variant="outline">Two</Button>
|
|
938
|
+
</ButtonGroup>
|
|
939
|
+
```
|
|
940
|
+
|
|
941
|
+
### Input No Se Expande
|
|
942
|
+
|
|
943
|
+
**Problema**: Input dentro del grupo no toma espacio disponible.
|
|
944
|
+
|
|
945
|
+
**Solución**:
|
|
946
|
+
|
|
947
|
+
```tsx
|
|
948
|
+
// ✅ El input automáticamente usa flex-1
|
|
949
|
+
<ButtonGroup>
|
|
950
|
+
<Input placeholder="Search..." /> {/* Se expande automáticamente */}
|
|
951
|
+
<Button variant="outline" size="icon">
|
|
952
|
+
<Icon symbol="search" />
|
|
953
|
+
</Button>
|
|
954
|
+
</ButtonGroup>
|
|
955
|
+
```
|
|
956
|
+
|
|
957
|
+
### Spacing entre Grupos Anidados
|
|
958
|
+
|
|
959
|
+
**Problema**: No hay espacio entre grupos anidados.
|
|
960
|
+
|
|
961
|
+
**Solución**:
|
|
962
|
+
|
|
963
|
+
```tsx
|
|
964
|
+
// ✅ El spacing se aplica automáticamente
|
|
965
|
+
<ButtonGroup>
|
|
966
|
+
<ButtonGroup>
|
|
967
|
+
<Button variant="outline">1</Button>
|
|
968
|
+
<Button variant="outline">2</Button>
|
|
969
|
+
</ButtonGroup>
|
|
970
|
+
<ButtonGroup>
|
|
971
|
+
{" "}
|
|
972
|
+
{/* gap-2 automático */}
|
|
973
|
+
<Button variant="outline">3</Button>
|
|
974
|
+
<Button variant="outline">4</Button>
|
|
975
|
+
</ButtonGroup>
|
|
976
|
+
</ButtonGroup>
|
|
977
|
+
```
|
|
978
|
+
|
|
979
|
+
## Referencias
|
|
980
|
+
|
|
981
|
+
- CVA (Class Variance Authority): https://cva.style/docs
|
|
982
|
+
- ARIA Group Role: https://www.w3.org/TR/wai-aria-1.2/#group
|
|
983
|
+
- ARIA Toolbar: https://www.w3.org/WAI/ARIA/apg/patterns/toolbar/
|
|
984
|
+
- Button Component: Ver documentación de Button
|