@adamosuiteservices/ui 2.9.15 → 2.10.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 (125) hide show
  1. package/dist/accordion-rounded.cjs +12 -17
  2. package/dist/accordion-rounded.js +37 -46
  3. package/dist/accordion.cjs +8 -7
  4. package/dist/accordion.js +19 -17
  5. package/dist/alert.cjs +7 -3
  6. package/dist/alert.js +14 -10
  7. package/dist/avatar.cjs +2 -0
  8. package/dist/avatar.js +42 -40
  9. package/dist/badge.cjs +9 -2
  10. package/dist/badge.js +19 -12
  11. package/dist/breadcrumb.cjs +7 -3
  12. package/dist/breadcrumb.js +30 -24
  13. package/dist/{button-B7ZP4LZN.js → button-CUNnLccZ.js} +11 -6
  14. package/dist/{button-D-qFRXiM.cjs → button-CxGyLoeN.cjs} +7 -2
  15. package/dist/button-group.cjs +2 -0
  16. package/dist/button-group.js +15 -13
  17. package/dist/button.cjs +1 -1
  18. package/dist/button.js +1 -1
  19. package/dist/calendar.cjs +8 -6
  20. package/dist/calendar.js +507 -492
  21. package/dist/card.cjs +9 -4
  22. package/dist/card.js +8 -3
  23. package/dist/checkbox-CFGlO5wt.cjs +22 -0
  24. package/dist/{checkbox-DhBcmKze.js → checkbox-DsN1IQGA.js} +70 -68
  25. package/dist/checkbox.cjs +1 -1
  26. package/dist/checkbox.js +1 -1
  27. package/dist/combobox.cjs +10 -13
  28. package/dist/combobox.js +90 -99
  29. package/dist/components/ui/icon/icon.d.ts +1 -3
  30. package/dist/components/ui/icon/icon.stories.d.ts +0 -5
  31. package/dist/components/ui/spinner/spinner.d.ts +1 -1
  32. package/dist/components/ui/tooltip/tooltip.stories.d.ts +0 -1
  33. package/dist/context-menu.cjs +10 -8
  34. package/dist/context-menu.js +126 -120
  35. package/dist/custom-layered-styles.css +1 -1
  36. package/dist/dialog.cjs +10 -7
  37. package/dist/dialog.js +43 -40
  38. package/dist/dropdown-menu.cjs +25 -10
  39. package/dist/dropdown-menu.js +179 -166
  40. package/dist/icon-D0vVkV-A.js +35 -0
  41. package/dist/icon-SWksUOv5.cjs +6 -0
  42. package/dist/icon.cjs +1 -1
  43. package/dist/icon.js +1 -1
  44. package/dist/icons.css +1 -1
  45. package/dist/input-group.cjs +14 -6
  46. package/dist/input-group.js +25 -17
  47. package/dist/kbd.cjs +3 -3
  48. package/dist/kbd.js +1 -0
  49. package/dist/pagination.cjs +4 -4
  50. package/dist/pagination.js +41 -43
  51. package/dist/radio-group.cjs +5 -5
  52. package/dist/radio-group.js +44 -42
  53. package/dist/radius.css +1 -1
  54. package/dist/select.cjs +12 -13
  55. package/dist/select.js +182 -187
  56. package/dist/{sheet-Q3dBOQG-.js → sheet-DM7b3ckK.js} +18 -18
  57. package/dist/{sheet-CGahUP7F.cjs → sheet-DfRu4ByS.cjs} +4 -4
  58. package/dist/sheet.cjs +1 -1
  59. package/dist/sheet.js +1 -1
  60. package/dist/sidebar.cjs +6 -11
  61. package/dist/sidebar.js +38 -49
  62. package/dist/spinner.cjs +1 -6
  63. package/dist/spinner.js +10 -16
  64. package/dist/styles.css +1 -1
  65. package/dist/tabs-underline.cjs +6 -3
  66. package/dist/tabs-underline.js +8 -5
  67. package/dist/tabs.cjs +6 -3
  68. package/dist/tabs.js +8 -5
  69. package/dist/toggle.cjs +5 -2
  70. package/dist/toggle.js +14 -11
  71. package/docs/components/layout/sidebar.md +18 -23
  72. package/docs/components/ui/accordion-rounded.md +584 -583
  73. package/docs/components/ui/accordion.md +3 -1
  74. package/docs/components/ui/alert.md +650 -671
  75. package/docs/components/ui/avatar.md +587 -588
  76. package/docs/components/ui/badge.md +1019 -1024
  77. package/docs/components/ui/breadcrumb.md +14 -14
  78. package/docs/components/ui/button-group.md +69 -87
  79. package/docs/components/ui/button.md +17 -17
  80. package/docs/components/ui/calendar.md +6 -6
  81. package/docs/components/ui/card.md +21 -27
  82. package/docs/components/ui/collapsible.md +16 -13
  83. package/docs/components/ui/combobox.md +14 -14
  84. package/docs/components/ui/command.md +6 -6
  85. package/docs/components/ui/context-menu.md +23 -23
  86. package/docs/components/ui/dialog.md +16 -16
  87. package/docs/components/ui/dropdown-menu.md +44 -66
  88. package/docs/components/ui/hover-card.md +5 -5
  89. package/docs/components/ui/icon.md +87 -88
  90. package/docs/components/ui/input-group.md +523 -509
  91. package/docs/components/ui/kbd.md +8 -8
  92. package/docs/components/ui/label.md +5 -5
  93. package/docs/components/ui/pagination.md +5 -5
  94. package/docs/components/ui/popover.md +14 -14
  95. package/docs/components/ui/progress.md +2 -2
  96. package/docs/components/ui/radio-group.md +2 -2
  97. package/docs/components/ui/select.md +6 -6
  98. package/docs/components/ui/spinner.md +15 -15
  99. package/docs/components/ui/switch.md +15 -9
  100. package/docs/components/ui/tabs-underline.md +4 -4
  101. package/docs/components/ui/tabs.md +5 -5
  102. package/docs/components/ui/toggle.md +17 -23
  103. package/docs/components/ui/tooltip.md +7 -10
  104. package/package.json +1 -1
  105. package/dist/check-CLxNVljQ.cjs +0 -6
  106. package/dist/check-Ci0GjV-B.js +0 -11
  107. package/dist/checkbox-CdnZ8VFJ.cjs +0 -21
  108. package/dist/chevron-down-BqEHzml5.cjs +0 -6
  109. package/dist/chevron-down-CpVS2Z7w.js +0 -11
  110. package/dist/chevron-left-B8QsBNvc.cjs +0 -6
  111. package/dist/chevron-left-Eal-WYLp.js +0 -11
  112. package/dist/chevron-right-BpzggHsr.cjs +0 -6
  113. package/dist/chevron-right-Cnc2tB4-.js +0 -11
  114. package/dist/circle-CX7JIirj.cjs +0 -6
  115. package/dist/circle-DYv-7Qb9.js +0 -11
  116. package/dist/createLucideIcon-B_8CJpcQ.js +0 -94
  117. package/dist/createLucideIcon-CAtnV-yz.cjs +0 -21
  118. package/dist/ellipsis-CryjZKZn.js +0 -15
  119. package/dist/ellipsis-Ct9VTDOG.cjs +0 -6
  120. package/dist/icon-DTx6Y_mx.cjs +0 -6
  121. package/dist/icon-DmU_SEHC.js +0 -36
  122. package/dist/minus-C0pHPx21.cjs +0 -6
  123. package/dist/minus-DE-onYs2.js +0 -11
  124. package/dist/x-CBKgg4YL.cjs +0 -6
  125. package/dist/x-Dl66o_vF.js +0 -14
@@ -1,509 +1,523 @@
1
- # Input Group Component
2
-
3
- Combina inputs/textareas con addons (texto, iconos, botones). Perfecto para campos con unidades, prefijos, acciones integradas o información contextual adicional.
4
-
5
- ## Descripción
6
-
7
- El componente `InputGroup` combina inputs con prefijos, sufijos y addons.
8
-
9
- ## Importación
10
-
11
- ```typescript
12
- import {
13
- InputGroup,
14
- InputGroupAddon,
15
- InputGroupButton,
16
- InputGroupText,
17
- InputGroupInput,
18
- InputGroupTextarea,
19
- } from "@adamosuiteservices/ui/input-group";
20
- ```
21
-
22
- ## Uso Básico
23
-
24
- ```tsx
25
- <InputGroup>
26
- <InputGroup.Addon>https://</InputGroup.Addon>
27
- <Input placeholder="example.com" />
28
- </InputGroup>
29
- ```
30
-
31
- ## Con Icono
32
-
33
- ```tsx
34
- <InputGroup>
35
- <InputGroup.Icon>
36
- <SearchIcon />
37
- </InputGroup.Icon>
38
- <Input placeholder="Search..." />
39
- </InputGroup>
40
- ```
41
-
42
- ## Con Botón
43
-
44
- ```tsx
45
- <InputGroup>
46
- <Input placeholder="Enter email" />
47
- <InputGroup.Button>
48
- <Button>Subscribe</Button>
49
- </InputGroup.Button>
50
- </InputGroup>
51
- ```
52
-
53
- **Componentes**: 6 (InputGroup, InputGroupAddon, InputGroupButton, InputGroupText, InputGroupInput, InputGroupTextarea)
54
-
55
- ## Props Principales
56
-
57
- ### InputGroup (Root)
58
-
59
- | Prop | Tipo | Descripción |
60
- | --------------- | --------- | ------------------------------------- |
61
- | `data-disabled` | `boolean` | Marca el grupo como disabled (visual) |
62
- | `className` | `string` | Clases CSS adicionales |
63
-
64
- **Nota**: Acepta todas las props de `<div>`
65
-
66
- ### InputGroupAddon
67
-
68
- | Prop | Tipo | Default | Descripción |
69
- | ----------- | ---------------------------------------------------------------- | ---------------- | ---------------------- |
70
- | `align` | `"inline-start" \| "inline-end" \| "block-start" \| "block-end"` | `"inline-start"` | Posición del addon |
71
- | `className` | `string` | - | Clases CSS adicionales |
72
-
73
- **Positions**:
74
-
75
- - `inline-start`: Izquierda (horizontal)
76
- - `inline-end`: Derecha (horizontal)
77
- - `block-start`: Arriba (vertical)
78
- - `block-end`: Abajo (vertical)
79
-
80
- ### InputGroupButton
81
-
82
- | Prop | Tipo | Default | Descripción |
83
- | --------- | ---------------------------------------- | ---------- | ---------------------------------------- |
84
- | `size` | `"xs" \| "sm" \| "icon-xs" \| "icon-sm"` | `"xs"` | Tamaño del botón |
85
- | `variant` | Button variants | `"ghost"` | Variante del botón |
86
- | `type` | `string` | `"button"` | Tipo HTML del botón |
87
- | ...rest | - | - | Todas las props de Button excepto `size` |
88
-
89
- **Sizes**:
90
-
91
- - `xs`: h-6, px-2
92
- - `sm`: h-8, px-2.5
93
- - `icon-xs`: size-6 (solo icono)
94
- - `icon-sm`: size-8 (solo icono)
95
-
96
- ### InputGroupText
97
-
98
- | Prop | Tipo | Descripción |
99
- | ----------- | -------- | ---------------------- |
100
- | `className` | `string` | Clases CSS adicionales |
101
-
102
- **Uso**: Wrapper para texto estático, iconos, labels, etc.
103
-
104
- ### InputGroupInput
105
-
106
- | Prop | Tipo | Descripción |
107
- | ----------- | -------- | ------------------------------------ |
108
- | ...props | - | Todas las props nativas de `<input>` |
109
- | `className` | `string` | Clases CSS adicionales |
110
-
111
- **Nota**: Estilos internos: sin border, sin shadow, bg-transparent
112
-
113
- ### InputGroupTextarea
114
-
115
- | Prop | Tipo | Descripción |
116
- | ----------- | -------- | --------------------------------------- |
117
- | ...props | - | Todas las props nativas de `<textarea>` |
118
- | `className` | `string` | Clases CSS adicionales |
119
-
120
- **Nota**: Estilos internos: sin border, sin shadow, bg-transparent, resize-none, py-3
121
-
122
- ## Patrones de Uso
123
-
124
- ### Básico con Icono
125
-
126
- ```tsx
127
- import { Search } from "lucide-react";
128
-
129
- <InputGroup>
130
- <InputGroupInput placeholder="Search..." />
131
- <InputGroupAddon>
132
- <Search />
133
- </InputGroupAddon>
134
- </InputGroup>;
135
- ```
136
-
137
- ### Con Texto (Precio)
138
-
139
- ```tsx
140
- <InputGroup>
141
- <InputGroupAddon>
142
- <InputGroupText>$</InputGroupText>
143
- </InputGroupAddon>
144
- <InputGroupInput placeholder="0.00" type="number" />
145
- <InputGroupAddon align="inline-end">
146
- <InputGroupText>USD</InputGroupText>
147
- </InputGroupAddon>
148
- </InputGroup>
149
- ```
150
-
151
- ### Con Prefijo URL
152
-
153
- ```tsx
154
- <InputGroup>
155
- <InputGroupAddon>
156
- <InputGroupText>https://</InputGroupText>
157
- </InputGroupAddon>
158
- <InputGroupInput placeholder="example.com" />
159
- <InputGroupAddon align="inline-end">
160
- <InputGroupText>.com</InputGroupText>
161
- </InputGroupAddon>
162
- </InputGroup>
163
- ```
164
-
165
- ### Con Email Domain
166
-
167
- ```tsx
168
- <InputGroup>
169
- <InputGroupInput placeholder="Enter your username" />
170
- <InputGroupAddon align="inline-end">
171
- <InputGroupText>@company.com</InputGroupText>
172
- </InputGroupAddon>
173
- </InputGroup>
174
- ```
175
-
176
- ### Con Botón Copy
177
-
178
- ```tsx
179
- import { useState } from "react";
180
- import { Copy, Check } from "lucide-react";
181
-
182
- function App() {
183
- const [isCopied, setIsCopied] = useState(false);
184
-
185
- const handleCopy = () => {
186
- navigator.clipboard.writeText("https://x.com/shadcn");
187
- setIsCopied(true);
188
- setTimeout(() => setIsCopied(false), 2000);
189
- };
190
-
191
- return (
192
- <InputGroup>
193
- <InputGroupInput
194
- placeholder="https://x.com/shadcn"
195
- readOnly
196
- defaultValue="https://x.com/shadcn"
197
- />
198
- <InputGroupAddon align="inline-end">
199
- <InputGroupButton aria-label="Copy" size="icon-xs" onClick={handleCopy}>
200
- {isCopied ? <Check /> : <Copy />}
201
- </InputGroupButton>
202
- </InputGroupAddon>
203
- </InputGroup>
204
- );
205
- }
206
- ```
207
-
208
- ### Con Botón Search
209
-
210
- ```tsx
211
- <InputGroup>
212
- <InputGroupInput placeholder="Type to search..." />
213
- <InputGroupAddon align="inline-end">
214
- <InputGroupButton variant="secondary">Search</InputGroupButton>
215
- </InputGroupAddon>
216
- </InputGroup>
217
- ```
218
-
219
- ### Con Tooltip
220
-
221
- ```tsx
222
- import { Info } from "lucide-react";
223
- import {
224
- Tooltip,
225
- TooltipContent,
226
- TooltipTrigger,
227
- } from "@adamosuiteservices/ui/tooltip";
228
-
229
- <InputGroup>
230
- <InputGroupInput placeholder="Enter password" type="password" />
231
- <InputGroupAddon align="inline-end">
232
- <Tooltip>
233
- <TooltipTrigger asChild>
234
- <InputGroupButton variant="ghost" size="icon-xs">
235
- <Info />
236
- </InputGroupButton>
237
- </TooltipTrigger>
238
- <TooltipContent>
239
- <p>Password must be at least 8 characters</p>
240
- </TooltipContent>
241
- </Tooltip>
242
- </InputGroupAddon>
243
- </InputGroup>;
244
- ```
245
-
246
- ### Password Toggle
247
-
248
- ```tsx
249
- import { useState } from "react";
250
- import { Eye, EyeOff } from "lucide-react";
251
- import { Label } from "@adamosuiteservices/ui/label";
252
-
253
- function App() {
254
- const [showPassword, setShowPassword] = useState(false);
255
-
256
- return (
257
- <div className="w-full max-w-sm">
258
- <Label htmlFor="password">Password</Label>
259
- <InputGroup className="mt-2">
260
- <InputGroupInput
261
- id="password"
262
- type={showPassword ? "text" : "password"}
263
- placeholder="Enter your password"
264
- />
265
- <InputGroupAddon align="inline-end">
266
- <InputGroupButton
267
- variant="ghost"
268
- size="icon-xs"
269
- onClick={() => setShowPassword(!showPassword)}
270
- aria-label={showPassword ? "Hide password" : "Show password"}
271
- >
272
- {showPassword ? <EyeOff /> : <Eye />}
273
- </InputGroupButton>
274
- </InputGroupAddon>
275
- </InputGroup>
276
- </div>
277
- );
278
- }
279
- ```
280
-
281
- ### Con Spinner (Loading)
282
-
283
- ```tsx
284
- import { Spinner } from "@adamosuiteservices/ui/spinner";
285
-
286
- <InputGroup data-disabled>
287
- <InputGroupInput placeholder="Searching..." disabled />
288
- <InputGroupAddon align="inline-end">
289
- <Spinner />
290
- </InputGroupAddon>
291
- </InputGroup>;
292
- ```
293
-
294
- ### Con Dropdown
295
-
296
- ```tsx
297
- import { MoreHorizontal } from "lucide-react";
298
- import {
299
- DropdownMenu,
300
- DropdownMenuContent,
301
- DropdownMenuItem,
302
- DropdownMenuTrigger,
303
- } from "@adamosuiteservices/ui/dropdown-menu";
304
-
305
- <InputGroup>
306
- <InputGroupInput placeholder="Enter file name" />
307
- <InputGroupAddon align="inline-end">
308
- <DropdownMenu>
309
- <DropdownMenuTrigger asChild>
310
- <InputGroupButton variant="ghost" size="icon-xs">
311
- <MoreHorizontal />
312
- </InputGroupButton>
313
- </DropdownMenuTrigger>
314
- <DropdownMenuContent align="end">
315
- <DropdownMenuItem>Settings</DropdownMenuItem>
316
- <DropdownMenuItem>Copy path</DropdownMenuItem>
317
- <DropdownMenuItem>Open location</DropdownMenuItem>
318
- </DropdownMenuContent>
319
- </DropdownMenu>
320
- </InputGroupAddon>
321
- </InputGroup>;
322
- ```
323
-
324
- ### Con Textarea
325
-
326
- ```tsx
327
- <InputGroup>
328
- <InputGroupTextarea
329
- placeholder="console.log('Hello, world!');"
330
- className="min-h-[200px]"
331
- />
332
- <InputGroupAddon align="block-end" className="border-t">
333
- <InputGroupText>Line 1, Column 1</InputGroupText>
334
- <InputGroupButton size="sm" className="ml-auto" variant="default">
335
- Run <Send />
336
- </InputGroupButton>
337
- </InputGroupAddon>
338
- </InputGroup>
339
- ```
340
-
341
- ### Con Label (Block Start)
342
-
343
- ```tsx
344
- import { Label } from "@adamosuiteservices/ui/label";
345
- import { Info } from "lucide-react";
346
-
347
- <InputGroup>
348
- <InputGroupInput id="email" placeholder="shadcn@vercel.com" />
349
- <InputGroupAddon align="block-start">
350
- <Label htmlFor="email" className="text-foreground">
351
- Email
352
- </Label>
353
- <Tooltip>
354
- <TooltipTrigger asChild>
355
- <InputGroupButton
356
- variant="ghost"
357
- className="ml-auto rounded-full"
358
- size="icon-xs"
359
- >
360
- <Info />
361
- </InputGroupButton>
362
- </TooltipTrigger>
363
- <TooltipContent>
364
- <p>We'll use this to send you notifications</p>
365
- </TooltipContent>
366
- </Tooltip>
367
- </InputGroupAddon>
368
- </InputGroup>;
369
- ```
370
-
371
- ### Chat Input Complex
372
-
373
- ```tsx
374
- import { useState } from "react";
375
- import { Plus, Forward } from "lucide-react";
376
- import { Spinner } from "@adamosuiteservices/ui/spinner";
377
-
378
- function App() {
379
- const [message, setMessage] = useState("");
380
- const [isLoading, setIsLoading] = useState(false);
381
-
382
- const handleSend = () => {
383
- if (!message.trim()) return;
384
- setIsLoading(true);
385
- setTimeout(() => {
386
- setMessage("");
387
- setIsLoading(false);
388
- }, 1000);
389
- };
390
-
391
- return (
392
- <InputGroup>
393
- <InputGroupTextarea
394
- placeholder="Ask, Search or Chat..."
395
- value={message}
396
- onChange={(e) => setMessage(e.target.value)}
397
- className="min-h-[60px]"
398
- />
399
- <InputGroupAddon align="block-end">
400
- <InputGroupButton
401
- variant="outline"
402
- className="rounded-full"
403
- size="icon-xs"
404
- >
405
- <Plus />
406
- </InputGroupButton>
407
-
408
- <InputGroupText className="ml-auto">
409
- {message.length}/280
410
- </InputGroupText>
411
-
412
- <InputGroupButton
413
- variant="default"
414
- className="rounded-full"
415
- size="icon-xs"
416
- onClick={handleSend}
417
- disabled={!message.trim() || isLoading}
418
- >
419
- {isLoading ? <Spinner /> : <Forward />}
420
- </InputGroupButton>
421
- </InputGroupAddon>
422
- </InputGroup>
423
- );
424
- }
425
- ```
426
-
427
- ## Casos de Uso Comunes
428
-
429
- **Price inputs**: Con $ prefix y USD suffix
430
- **URL inputs**: Con https:// prefix y dominio suffix
431
- **Email inputs**: Con @domain.com suffix
432
- **Search bars**: Con icono search y resultados count
433
- **Copy fields**: Con botón copy integrado
434
- **Password fields**: Con toggle show/hide
435
- **Chat inputs**: Con múltiples controles y contador
436
- **Code editors**: Con textarea y toolbars en block-start/end
437
-
438
- ## Estados y Data Attributes
439
-
440
- ### InputGroup States
441
-
442
- - **Disabled**: `data-disabled` → `opacity-50` en addons
443
- - **Focus**: `has-[input:focus-visible]` ring y border aplicados al grupo
444
- - **Invalid**: `has-[input[aria-invalid=true]]` ring destructive y border
445
-
446
- ### Focus Behavior
447
-
448
- El grupo aplica estilos focus cuando el input/textarea interno recibe focus, no el grupo en sí.
449
-
450
- ### Click Behavior (Addon)
451
-
452
- Los addons hacen focus al input cuando se hace click en el addon (no en botones).
453
-
454
- ## Interacción
455
-
456
- - **Click addon**: Focus automático en el input (excepto botones)
457
- - **Keyboard**: Navegación normal del input/textarea
458
- - **Disabled**: Visual `data-disabled`, input debe estar `disabled` también
459
- - ✅ **Auto-height**: Con textarea, el grupo ajusta altura automáticamente
460
-
461
- ## Accesibilidad
462
-
463
- - ✅ **Role**: `role="group"` en InputGroup y InputGroupAddon
464
- - **Labels**: Usa Label dentro de InputGroupAddon o externo con `htmlFor`
465
- - ✅ **ARIA**: Input/textarea maneja `aria-invalid`, `aria-describedby`
466
- - **Focus**: Focus visible en el grupo cuando input interno tiene focus
467
- - ✅ **Keyboard**: Navegación nativa del input/textarea
468
- - ⚠️ **Buttons**: Usa `aria-label` en InputGroupButton para iconos
469
-
470
- ## Notas de Implementación
471
-
472
- - **Container**: InputGroup es el wrapper con border, shadow, focus ring
473
- - **Input/Textarea**: Sin border, sin shadow, sin focus ring (aplicado al grupo)
474
- - **Auto-height**: Grupo tiene `has-[>textarea]:h-auto` para textareas
475
- - **Alignment**: CVA con 4 posiciones (inline-start, inline-end, block-start, block-end)
476
- - **Click delegation**: Addon hace focus al input al click (excepto en botones)
477
- - **Variants**: InputGroupButton usa CVA para 4 tamaños custom
478
- - **Order**: Addon usa `order-first` o `order-last` para posicionamiento
479
- - **Focus detection**: CSS `:has()` para aplicar focus ring al grupo
480
- - **Portal**: No usa portal, todo inline
481
-
482
- ## Alineamiento Detallado
483
-
484
- ### Inline (Horizontal)
485
-
486
- - **inline-start**: `pl-3`, botones con `ml-[-0.45rem]`, orden `order-first`
487
- - **inline-end**: `pr-3`, botones con `mr-[-0.45rem]`, orden `order-last`
488
-
489
- ### Block (Vertical)
490
-
491
- - **block-start**: `w-full`, `px-3 pt-3`, `order-first`, borders con `border-b`
492
- - **block-end**: `w-full`, `px-3 pb-3`, `order-last`, borders con `border-t`
493
-
494
- ## Troubleshooting
495
-
496
- **Addon no clickea al input**: Verifica que InputGroupAddon tenga el onClick handler (default)
497
- **Focus ring no aparece**: El input interno debe tener `data-slot="input-group-control"`
498
- **Textarea no ajusta altura**: Verifica que uses `InputGroupTextarea` no `InputGroupInput`
499
- **Botón muy grande**: Usa `size="xs"` o `size="icon-xs"` en InputGroupButton
500
- **Addon mal posicionado**: Verifica `align` prop (inline-start/end, block-start/end)
501
- **Border doble**: InputGroupInput/Textarea tienen `border-0`, no uses Input/Textarea directamente
502
- **Invalid state no funciona**: El input interno necesita `aria-invalid`, grupo detecta con `:has()`
503
- **Disabled no funciona**: Aplica `disabled` al input Y `data-disabled` al InputGroup
504
- **Spacing inconsistente**: Addon usa padding específico por align, no modifiques sin CVA
505
-
506
- ## Referencias
507
-
508
- - **shadcn/ui Input**: <https://ui.shadcn.com/docs/components/input>
509
- - **Radix UI Label**: <https://www.radix-ui.com/primitives/docs/components/label>
1
+ # Input Group Component
2
+
3
+ Combina inputs/textareas con addons (texto, iconos, botones). Perfecto para campos con unidades, prefijos, acciones integradas o información contextual adicional.
4
+
5
+ ## Descripción
6
+
7
+ El componente `InputGroup` combina inputs con prefijos, sufijos y addons.
8
+
9
+ ## Importación
10
+
11
+ ```typescript
12
+ import {
13
+ InputGroup,
14
+ InputGroupAddon,
15
+ InputGroupButton,
16
+ InputGroupText,
17
+ InputGroupInput,
18
+ InputGroupTextarea,
19
+ } from "@adamosuiteservices/ui/input-group";
20
+ ```
21
+
22
+ ## Uso Básico
23
+
24
+ ```tsx
25
+ <InputGroup>
26
+ <InputGroup.Addon>https://</InputGroup.Addon>
27
+ <Input placeholder="example.com" />
28
+ </InputGroup>
29
+ ```
30
+
31
+ ## Con Icono
32
+
33
+ ```tsx
34
+ <InputGroup>
35
+ <InputGroup.Icon>
36
+ <SearchIcon />
37
+ </InputGroup.Icon>
38
+ <Input placeholder="Search..." />
39
+ </InputGroup>
40
+ ```
41
+
42
+ ## Con Botón
43
+
44
+ ```tsx
45
+ <InputGroup>
46
+ <Input placeholder="Enter email" />
47
+ <InputGroup.Button>
48
+ <Button>Subscribe</Button>
49
+ </InputGroup.Button>
50
+ </InputGroup>
51
+ ```
52
+
53
+ **Componentes**: 6 (InputGroup, InputGroupAddon, InputGroupButton, InputGroupText, InputGroupInput, InputGroupTextarea)
54
+
55
+ ## Props Principales
56
+
57
+ ### InputGroup (Root)
58
+
59
+ | Prop | Tipo | Descripción |
60
+ | --------------- | --------- | ------------------------------------- |
61
+ | `data-disabled` | `boolean` | Marca el grupo como disabled (visual) |
62
+ | `className` | `string` | Clases CSS adicionales |
63
+
64
+ **Nota**: Acepta todas las props de `<div>`
65
+
66
+ ### InputGroupAddon
67
+
68
+ | Prop | Tipo | Default | Descripción |
69
+ | ----------- | ---------------------------------------------------------------- | ---------------- | ---------------------- |
70
+ | `align` | `"inline-start" \| "inline-end" \| "block-start" \| "block-end"` | `"inline-start"` | Posición del addon |
71
+ | `className` | `string` | - | Clases CSS adicionales |
72
+
73
+ **Positions**:
74
+
75
+ - `inline-start`: Izquierda (horizontal)
76
+ - `inline-end`: Derecha (horizontal)
77
+ - `block-start`: Arriba (vertical)
78
+ - `block-end`: Abajo (vertical)
79
+
80
+ ### InputGroupButton
81
+
82
+ | Prop | Tipo | Default | Descripción |
83
+ | --------- | ---------------------------------------- | ---------- | ---------------------------------------- |
84
+ | `size` | `"xs" \| "sm" \| "icon-xs" \| "icon-sm"` | `"xs"` | Tamaño del botón |
85
+ | `variant` | Button variants | `"ghost"` | Variante del botón |
86
+ | `type` | `string` | `"button"` | Tipo HTML del botón |
87
+ | ...rest | - | - | Todas las props de Button excepto `size` |
88
+
89
+ **Sizes**:
90
+
91
+ - `xs`: h-6, px-2
92
+ - `sm`: h-8, px-2.5
93
+ - `icon-xs`: size-6 (solo icono)
94
+ - `icon-sm`: size-8 (solo icono)
95
+
96
+ ### InputGroupText
97
+
98
+ | Prop | Tipo | Descripción |
99
+ | ----------- | -------- | ---------------------- |
100
+ | `className` | `string` | Clases CSS adicionales |
101
+
102
+ **Uso**: Wrapper para texto estático, iconos, labels, etc.
103
+
104
+ ### InputGroupInput
105
+
106
+ | Prop | Tipo | Descripción |
107
+ | ----------- | -------- | ------------------------------------ |
108
+ | ...props | - | Todas las props nativas de `<input>` |
109
+ | `className` | `string` | Clases CSS adicionales |
110
+
111
+ **Nota**: Estilos internos: sin border, sin shadow, bg-transparent
112
+
113
+ ### InputGroupTextarea
114
+
115
+ | Prop | Tipo | Descripción |
116
+ | ----------- | -------- | --------------------------------------- |
117
+ | ...props | - | Todas las props nativas de `<textarea>` |
118
+ | `className` | `string` | Clases CSS adicionales |
119
+
120
+ **Nota**: Estilos internos: sin border, sin shadow, bg-transparent, resize-none, py-3
121
+
122
+ ## Patrones de Uso
123
+
124
+ ### Básico con Icono
125
+
126
+ ```tsx
127
+ import { Icon } from "@adamosuiteservices/ui/icon";
128
+
129
+ <InputGroup>
130
+ <InputGroupInput placeholder="Search..." />
131
+ <InputGroupAddon>
132
+ <Icon symbol="search" className="text-lg" />
133
+ </InputGroupAddon>
134
+ </InputGroup>;
135
+ ```
136
+
137
+ ### Con Texto (Precio)
138
+
139
+ ```tsx
140
+ <InputGroup>
141
+ <InputGroupAddon>
142
+ <InputGroupText>$</InputGroupText>
143
+ </InputGroupAddon>
144
+ <InputGroupInput placeholder="0.00" type="number" />
145
+ <InputGroupAddon align="inline-end">
146
+ <InputGroupText>USD</InputGroupText>
147
+ </InputGroupAddon>
148
+ </InputGroup>
149
+ ```
150
+
151
+ ### Con Prefijo URL
152
+
153
+ ```tsx
154
+ <InputGroup>
155
+ <InputGroupAddon>
156
+ <InputGroupText>https://</InputGroupText>
157
+ </InputGroupAddon>
158
+ <InputGroupInput placeholder="example.com" />
159
+ <InputGroupAddon align="inline-end">
160
+ <InputGroupText>.com</InputGroupText>
161
+ </InputGroupAddon>
162
+ </InputGroup>
163
+ ```
164
+
165
+ ### Con Email Domain
166
+
167
+ ```tsx
168
+ <InputGroup>
169
+ <InputGroupInput placeholder="Enter your username" />
170
+ <InputGroupAddon align="inline-end">
171
+ <InputGroupText>@company.com</InputGroupText>
172
+ </InputGroupAddon>
173
+ </InputGroup>
174
+ ```
175
+
176
+ ### Con Botón Copy
177
+
178
+ ```tsx
179
+ import { useState } from "react";
180
+ import { Icon } from "@adamosuiteservices/ui/icon";
181
+
182
+ function App() {
183
+ const [isCopied, setIsCopied] = useState(false);
184
+
185
+ const handleCopy = () => {
186
+ navigator.clipboard.writeText("https://x.com/shadcn");
187
+ setIsCopied(true);
188
+ setTimeout(() => setIsCopied(false), 2000);
189
+ };
190
+
191
+ return (
192
+ <InputGroup>
193
+ <InputGroupInput
194
+ placeholder="https://x.com/shadcn"
195
+ readOnly
196
+ defaultValue="https://x.com/shadcn"
197
+ />
198
+ <InputGroupAddon align="inline-end">
199
+ <InputGroupButton aria-label="Copy" size="icon-xs" onClick={handleCopy}>
200
+ {isCopied ? (
201
+ <Icon symbol="check" className="text-base" />
202
+ ) : (
203
+ <Icon symbol="content_copy" className="text-base" />
204
+ )}
205
+ </InputGroupButton>
206
+ </InputGroupAddon>
207
+ </InputGroup>
208
+ );
209
+ }
210
+ ```
211
+
212
+ ### Con Botón Search
213
+
214
+ ```tsx
215
+ <InputGroup>
216
+ <InputGroupInput placeholder="Type to search..." />
217
+ <InputGroupAddon align="inline-end">
218
+ <InputGroupButton variant="secondary">Search</InputGroupButton>
219
+ </InputGroupAddon>
220
+ </InputGroup>
221
+ ```
222
+
223
+ ### Con Tooltip
224
+
225
+ ```tsx
226
+ import { Icon } from "@adamosuiteservices/ui/icon";
227
+ import {
228
+ Tooltip,
229
+ TooltipContent,
230
+ TooltipTrigger,
231
+ } from "@adamosuiteservices/ui/tooltip";
232
+
233
+ <InputGroup>
234
+ <InputGroupInput placeholder="Enter password" type="password" />
235
+ <InputGroupAddon align="inline-end">
236
+ <Tooltip>
237
+ <TooltipTrigger asChild>
238
+ <InputGroupButton variant="ghost" size="icon-xs">
239
+ <Icon symbol="info" className="text-base" />
240
+ </InputGroupButton>
241
+ </TooltipTrigger>
242
+ <TooltipContent>
243
+ <p>Password must be at least 8 characters</p>
244
+ </TooltipContent>
245
+ </Tooltip>
246
+ </InputGroupAddon>
247
+ </InputGroup>;
248
+ ```
249
+
250
+ ### Password Toggle
251
+
252
+ ```tsx
253
+ import { useState } from "react";
254
+ import { Icon } from "@adamosuiteservices/ui/icon";
255
+ import { Label } from "@adamosuiteservices/ui/label";
256
+
257
+ function App() {
258
+ const [showPassword, setShowPassword] = useState(false);
259
+
260
+ return (
261
+ <div className="w-full max-w-sm">
262
+ <Label htmlFor="password">Password</Label>
263
+ <InputGroup className="mt-2">
264
+ <InputGroupInput
265
+ id="password"
266
+ type={showPassword ? "text" : "password"}
267
+ placeholder="Enter your password"
268
+ />
269
+ <InputGroupAddon align="inline-end">
270
+ <InputGroupButton
271
+ variant="ghost"
272
+ size="icon-xs"
273
+ onClick={() => setShowPassword(!showPassword)}
274
+ aria-label={showPassword ? "Hide password" : "Show password"}
275
+ >
276
+ {showPassword ? (
277
+ <Icon symbol="visibility_off" className="text-base" />
278
+ ) : (
279
+ <Icon symbol="visibility" className="text-base" />
280
+ )}
281
+ </InputGroupButton>
282
+ </InputGroupAddon>
283
+ </InputGroup>
284
+ </div>
285
+ );
286
+ }
287
+ ```
288
+
289
+ ### Con Spinner (Loading)
290
+
291
+ ```tsx
292
+ import { Spinner } from "@adamosuiteservices/ui/spinner";
293
+
294
+ <InputGroup data-disabled>
295
+ <InputGroupInput placeholder="Searching..." disabled />
296
+ <InputGroupAddon align="inline-end">
297
+ <Spinner />
298
+ </InputGroupAddon>
299
+ </InputGroup>;
300
+ ```
301
+
302
+ ### Con Dropdown
303
+
304
+ ```tsx
305
+ import { Icon } from "@adamosuiteservices/ui/icon";
306
+ import {
307
+ DropdownMenu,
308
+ DropdownMenuContent,
309
+ DropdownMenuItem,
310
+ DropdownMenuTrigger,
311
+ } from "@adamosuiteservices/ui/dropdown-menu";
312
+
313
+ <InputGroup>
314
+ <InputGroupInput placeholder="Enter file name" />
315
+ <InputGroupAddon align="inline-end">
316
+ <DropdownMenu>
317
+ <DropdownMenuTrigger asChild>
318
+ <InputGroupButton variant="ghost" size="icon-xs">
319
+ <Icon symbol="more_horiz" className="text-base" />
320
+ </InputGroupButton>
321
+ </DropdownMenuTrigger>
322
+ <DropdownMenuContent align="end">
323
+ <DropdownMenuItem>Settings</DropdownMenuItem>
324
+ <DropdownMenuItem>Copy path</DropdownMenuItem>
325
+ <DropdownMenuItem>Open location</DropdownMenuItem>
326
+ </DropdownMenuContent>
327
+ </DropdownMenu>
328
+ </InputGroupAddon>
329
+ </InputGroup>;
330
+ ```
331
+
332
+ ### Con Textarea
333
+
334
+ ```tsx
335
+ import { Icon } from "@adamosuiteservices/ui/icon";
336
+
337
+ <InputGroup>
338
+ <InputGroupTextarea
339
+ placeholder="console.log('Hello, world!');"
340
+ className="min-h-[200px]"
341
+ />
342
+ <InputGroupAddon align="block-end" className="border-t">
343
+ <InputGroupText>Line 1, Column 1</InputGroupText>
344
+ <InputGroupButton size="sm" className="ml-auto" variant="default">
345
+ Run <Icon symbol="send" className="text-base" />
346
+ </InputGroupButton>
347
+ </InputGroupAddon>
348
+ </InputGroup>;
349
+ ```
350
+
351
+ ### Con Label (Block Start)
352
+
353
+ ```tsx
354
+ import { Icon } from "@adamosuiteservices/ui/icon";
355
+ import { Label } from "@adamosuiteservices/ui/label";
356
+
357
+ <InputGroup>
358
+ <InputGroupInput id="email" placeholder="shadcn@vercel.com" />
359
+ <InputGroupAddon align="block-start">
360
+ <Label htmlFor="email" className="text-foreground">
361
+ Email
362
+ </Label>
363
+ <Tooltip>
364
+ <TooltipTrigger asChild>
365
+ <InputGroupButton
366
+ variant="ghost"
367
+ className="ml-auto rounded-full"
368
+ size="icon-xs"
369
+ >
370
+ <Icon symbol="info" className="text-base" />
371
+ </InputGroupButton>
372
+ </TooltipTrigger>
373
+ <TooltipContent>
374
+ <p>We'll use this to send you notifications</p>
375
+ </TooltipContent>
376
+ </Tooltip>
377
+ </InputGroupAddon>
378
+ </InputGroup>;
379
+ ```
380
+
381
+ ### Chat Input Complex
382
+
383
+ ```tsx
384
+ import { useState } from "react";
385
+ import { Icon } from "@adamosuiteservices/ui/icon";
386
+ import { Spinner } from "@adamosuiteservices/ui/spinner";
387
+
388
+ function App() {
389
+ const [message, setMessage] = useState("");
390
+ const [isLoading, setIsLoading] = useState(false);
391
+
392
+ const handleSend = () => {
393
+ if (!message.trim()) return;
394
+ setIsLoading(true);
395
+ setTimeout(() => {
396
+ setMessage("");
397
+ setIsLoading(false);
398
+ }, 1000);
399
+ };
400
+
401
+ return (
402
+ <InputGroup>
403
+ <InputGroupTextarea
404
+ placeholder="Ask, Search or Chat..."
405
+ value={message}
406
+ onChange={(e) => setMessage(e.target.value)}
407
+ className="min-h-[60px]"
408
+ />
409
+ <InputGroupAddon align="block-end">
410
+ <InputGroupButton
411
+ variant="outline"
412
+ className="rounded-full"
413
+ size="icon-xs"
414
+ >
415
+ <Icon symbol="add" className="text-base" />
416
+ </InputGroupButton>
417
+
418
+ <InputGroupText className="ml-auto">
419
+ {message.length}/280
420
+ </InputGroupText>
421
+
422
+ <InputGroupButton
423
+ variant="default"
424
+ className="rounded-full"
425
+ size="icon-xs"
426
+ onClick={handleSend}
427
+ disabled={!message.trim() || isLoading}
428
+ >
429
+ {isLoading ? (
430
+ <Spinner />
431
+ ) : (
432
+ <Icon symbol="forward" className="text-base" />
433
+ )}
434
+ </InputGroupButton>
435
+ </InputGroupAddon>
436
+ </InputGroup>
437
+ );
438
+ }
439
+ ```
440
+
441
+ ## Casos de Uso Comunes
442
+
443
+ **Price inputs**: Con $ prefix y USD suffix
444
+ **URL inputs**: Con https:// prefix y dominio suffix
445
+ **Email inputs**: Con @domain.com suffix
446
+ **Search bars**: Con icono search y resultados count
447
+ **Copy fields**: Con botón copy integrado
448
+ **Password fields**: Con toggle show/hide
449
+ **Chat inputs**: Con múltiples controles y contador
450
+ **Code editors**: Con textarea y toolbars en block-start/end
451
+
452
+ ## Estados y Data Attributes
453
+
454
+ ### InputGroup States
455
+
456
+ - **Disabled**: `data-disabled` `opacity-50` en addons
457
+ - **Focus**: `has-[input:focus-visible]` ring y border aplicados al grupo
458
+ - **Invalid**: `has-[input[aria-invalid=true]]` ring destructive y border
459
+
460
+ ### Focus Behavior
461
+
462
+ El grupo aplica estilos focus cuando el input/textarea interno recibe focus, no el grupo en sí.
463
+
464
+ ### Click Behavior (Addon)
465
+
466
+ Los addons hacen focus al input cuando se hace click en el addon (no en botones).
467
+
468
+ ## Interacción
469
+
470
+ - **Click addon**: Focus automático en el input (excepto botones)
471
+ - ✅ **Keyboard**: Navegación normal del input/textarea
472
+ - **Disabled**: Visual `data-disabled`, input debe estar `disabled` también
473
+ - **Auto-height**: Con textarea, el grupo ajusta altura automáticamente
474
+
475
+ ## Accesibilidad
476
+
477
+ - **Role**: `role="group"` en InputGroup y InputGroupAddon
478
+ - **Labels**: Usa Label dentro de InputGroupAddon o externo con `htmlFor`
479
+ - **ARIA**: Input/textarea maneja `aria-invalid`, `aria-describedby`
480
+ - **Focus**: Focus visible en el grupo cuando input interno tiene focus
481
+ - ✅ **Keyboard**: Navegación nativa del input/textarea
482
+ - ⚠️ **Buttons**: Usa `aria-label` en InputGroupButton para iconos
483
+
484
+ ## Notas de Implementación
485
+
486
+ - **Container**: InputGroup es el wrapper con border, shadow, focus ring
487
+ - **Input/Textarea**: Sin border, sin shadow, sin focus ring (aplicado al grupo)
488
+ - **Auto-height**: Grupo tiene `has-[>textarea]:h-auto` para textareas
489
+ - **Alignment**: CVA con 4 posiciones (inline-start, inline-end, block-start, block-end)
490
+ - **Click delegation**: Addon hace focus al input al click (excepto en botones)
491
+ - **Variants**: InputGroupButton usa CVA para 4 tamaños custom
492
+ - **Order**: Addon usa `order-first` o `order-last` para posicionamiento
493
+ - **Focus detection**: CSS `:has()` para aplicar focus ring al grupo
494
+ - **Portal**: No usa portal, todo inline
495
+
496
+ ## Alineamiento Detallado
497
+
498
+ ### Inline (Horizontal)
499
+
500
+ - **inline-start**: `pl-3`, botones con `ml-[-0.45rem]`, orden `order-first`
501
+ - **inline-end**: `pr-3`, botones con `mr-[-0.45rem]`, orden `order-last`
502
+
503
+ ### Block (Vertical)
504
+
505
+ - **block-start**: `w-full`, `px-3 pt-3`, `order-first`, borders con `border-b`
506
+ - **block-end**: `w-full`, `px-3 pb-3`, `order-last`, borders con `border-t`
507
+
508
+ ## Troubleshooting
509
+
510
+ **Addon no clickea al input**: Verifica que InputGroupAddon tenga el onClick handler (default)
511
+ **Focus ring no aparece**: El input interno debe tener `data-slot="input-group-control"`
512
+ **Textarea no ajusta altura**: Verifica que uses `InputGroupTextarea` no `InputGroupInput`
513
+ **Botón muy grande**: Usa `size="xs"` o `size="icon-xs"` en InputGroupButton
514
+ **Addon mal posicionado**: Verifica `align` prop (inline-start/end, block-start/end)
515
+ **Border doble**: InputGroupInput/Textarea tienen `border-0`, no uses Input/Textarea directamente
516
+ **Invalid state no funciona**: El input interno necesita `aria-invalid`, grupo detecta con `:has()`
517
+ **Disabled no funciona**: Aplica `disabled` al input Y `data-disabled` al InputGroup
518
+ **Spacing inconsistente**: Addon usa padding específico por align, no modifiques sin CVA
519
+
520
+ ## Referencias
521
+
522
+ - **shadcn/ui Input**: <https://ui.shadcn.com/docs/components/input>
523
+ - **Radix UI Label**: <https://www.radix-ui.com/primitives/docs/components/label>