@adamosuiteservices/ui 2.11.20 → 2.12.0

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