@adamosuiteservices/ui 2.11.17 → 2.11.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/dist/accordion-rounded.cjs +1 -1
  2. package/dist/accordion-rounded.js +1 -1
  3. package/dist/badge.cjs +3 -3
  4. package/dist/badge.js +2 -2
  5. package/dist/breadcrumb.cjs +5 -5
  6. package/dist/breadcrumb.js +16 -16
  7. package/dist/components/ui/date-picker-selector/date-picker-selector.d.ts +2 -2
  8. package/dist/components/ui/sheet/sheet.d.ts +2 -1
  9. package/dist/date-picker-selector.cjs +1 -1
  10. package/dist/date-picker-selector.js +29 -30
  11. package/dist/dialog.cjs +1 -1
  12. package/dist/dialog.js +1 -1
  13. package/dist/select.cjs +2 -2
  14. package/dist/select.js +2 -2
  15. package/dist/{sheet-DVT_djHX.cjs → sheet-CvcCaGSl.cjs} +11 -11
  16. package/dist/{sheet-CPf9Guon.js → sheet-IRIc3TJ1.js} +33 -19
  17. package/dist/sheet.cjs +1 -1
  18. package/dist/sheet.js +7 -6
  19. package/dist/sidebar.cjs +1 -1
  20. package/dist/sidebar.js +1 -1
  21. package/dist/styles.css +1 -1
  22. package/dist/tabs-underline.cjs +3 -3
  23. package/dist/tabs-underline.js +13 -13
  24. package/dist/tabs.cjs +5 -4
  25. package/dist/tabs.js +3 -2
  26. package/dist/themes.css +1 -1
  27. package/docs/AI-GUIDE.md +321 -321
  28. package/docs/components/layout/sidebar.md +399 -399
  29. package/docs/components/layout/toaster.md +436 -436
  30. package/docs/components/ui/accordion-rounded.md +584 -584
  31. package/docs/components/ui/accordion.md +269 -269
  32. package/docs/components/ui/badge.md +2 -1
  33. package/docs/components/ui/button-group.md +984 -984
  34. package/docs/components/ui/button.md +1137 -1137
  35. package/docs/components/ui/calendar.md +1159 -1159
  36. package/docs/components/ui/card.md +1455 -1455
  37. package/docs/components/ui/checkbox.md +292 -292
  38. package/docs/components/ui/collapsible.md +323 -323
  39. package/docs/components/ui/command.md +454 -454
  40. package/docs/components/ui/context-menu.md +540 -540
  41. package/docs/components/ui/date-picker-selector.md +0 -2
  42. package/docs/components/ui/dialog.md +628 -628
  43. package/docs/components/ui/dropdown-menu.md +709 -709
  44. package/docs/components/ui/field.md +706 -706
  45. package/docs/components/ui/hover-card.md +446 -446
  46. package/docs/components/ui/input.md +362 -362
  47. package/docs/components/ui/kbd.md +434 -434
  48. package/docs/components/ui/label.md +359 -359
  49. package/docs/components/ui/pagination.md +650 -650
  50. package/docs/components/ui/popover.md +536 -536
  51. package/docs/components/ui/progress.md +182 -182
  52. package/docs/components/ui/radio-group.md +311 -311
  53. package/docs/components/ui/select.md +352 -352
  54. package/docs/components/ui/separator.md +214 -214
  55. package/docs/components/ui/sheet.md +174 -142
  56. package/docs/components/ui/skeleton.md +140 -140
  57. package/docs/components/ui/slider.md +341 -341
  58. package/docs/components/ui/spinner.md +170 -170
  59. package/docs/components/ui/switch.md +408 -408
  60. package/docs/components/ui/tabs-underline.md +106 -106
  61. package/docs/components/ui/tabs.md +122 -122
  62. package/docs/components/ui/textarea.md +243 -243
  63. package/docs/components/ui/toggle.md +237 -237
  64. package/docs/components/ui/tooltip.md +317 -317
  65. package/docs/components/ui/typography.md +280 -280
  66. 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