@adamosuiteservices/ui 2.11.21 → 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 (50) hide show
  1. package/dist/{button-BzVY7053.js → button-CMWUcYpz.js} +54 -54
  2. package/dist/button-CmLeuaI0.cjs +75 -0
  3. package/dist/button-group.cjs +6 -6
  4. package/dist/button-group.js +20 -20
  5. package/dist/button.cjs +1 -1
  6. package/dist/button.js +1 -1
  7. package/dist/{calendar-IAVS_7up.cjs → calendar-DUvRetFW.cjs} +1 -1
  8. package/dist/{calendar-BIttOd3g.js → calendar-yJWQTCer.js} +1 -1
  9. package/dist/calendar.cjs +1 -1
  10. package/dist/calendar.js +1 -1
  11. package/dist/combobox-BhDOduAb.cjs +40 -0
  12. package/dist/{combobox-DI-WK3Ng.js → combobox-CYOGlfQe.js} +91 -88
  13. package/dist/combobox.cjs +1 -1
  14. package/dist/combobox.js +1 -1
  15. package/dist/components/ui/button/button.d.ts +1 -1
  16. package/dist/components/ui/input-otp/index.d.ts +1 -0
  17. package/dist/components/ui/input-otp/input-otp.d.ts +11 -0
  18. package/dist/components/ui/input-otp.d.ts +11 -0
  19. package/dist/context-menu.cjs +38 -36
  20. package/dist/context-menu.js +54 -52
  21. package/dist/date-picker-selector.cjs +1 -1
  22. package/dist/date-picker-selector.js +4 -4
  23. package/dist/dropdown-menu.cjs +48 -52
  24. package/dist/dropdown-menu.js +164 -168
  25. package/dist/{input-DFvZLcgm.js → input-BCiOr4Fy.js} +7 -6
  26. package/dist/{input-CWDpI1Ua.cjs → input-Bz5k4w94.cjs} +7 -6
  27. package/dist/input-group.cjs +5 -5
  28. package/dist/input-group.js +10 -10
  29. package/dist/input-otp.cjs +68 -0
  30. package/dist/input-otp.js +384 -0
  31. package/dist/input.cjs +1 -1
  32. package/dist/input.js +1 -1
  33. package/dist/pagination.cjs +2 -2
  34. package/dist/pagination.js +4 -3
  35. package/dist/select.cjs +22 -20
  36. package/dist/select.js +112 -111
  37. package/dist/sidebar.cjs +1 -1
  38. package/dist/sidebar.js +1 -1
  39. package/dist/styles.css +1 -1
  40. package/docs/components/ui/button-group.md +986 -984
  41. package/docs/components/ui/button.md +1156 -1137
  42. package/docs/components/ui/combobox.md +33 -2
  43. package/docs/components/ui/command.md +484 -454
  44. package/docs/components/ui/context-menu.md +574 -540
  45. package/docs/components/ui/dropdown-menu.md +743 -709
  46. package/docs/components/ui/input.md +362 -362
  47. package/docs/components/ui/select.md +357 -352
  48. package/package.json +8 -3
  49. package/dist/button-B_VHdPPV.cjs +0 -76
  50. package/dist/combobox-DplJzBX6.cjs +0 -37
@@ -1,709 +1,743 @@
1
- # Dropdown Menu
2
-
3
- Menú desplegable activado por click, basado en Radix UI. Soporta submenús, checkboxes, radio groups, separadores, y shortcuts visuales. Similar a ContextMenu pero activado con click en lugar de click derecho.
4
-
5
- ## Importación
6
-
7
- ```tsx
8
- import {
9
- DropdownMenu,
10
- DropdownMenuTrigger,
11
- DropdownMenuContent,
12
- DropdownMenuItem,
13
- DropdownMenuCheckboxItem,
14
- DropdownMenuRadioGroup,
15
- DropdownMenuRadioItem,
16
- DropdownMenuLabel,
17
- DropdownMenuSeparator,
18
- DropdownMenuShortcut,
19
- DropdownMenuSub,
20
- DropdownMenuSubContent,
21
- DropdownMenuSubTrigger,
22
- DropdownMenuGroup,
23
- DropdownMenuPortal,
24
- } from "@adamosuiteservices/ui/dropdown-menu";
25
- ```
26
-
27
- ## Anatomía
28
-
29
- ```tsx
30
- <DropdownMenu>
31
- <DropdownMenuTrigger asChild>
32
- <Button>Open Menu</Button>
33
- </DropdownMenuTrigger>
34
- <DropdownMenuContent>
35
- <DropdownMenuLabel>My Account</DropdownMenuLabel>
36
- <DropdownMenuSeparator />
37
- <DropdownMenuItem>
38
- Profile
39
- <DropdownMenuShortcut>⇧⌘P</DropdownMenuShortcut>
40
- </DropdownMenuItem>
41
- <DropdownMenuCheckboxItem checked={showPanel}>
42
- Show Panel
43
- </DropdownMenuCheckboxItem>
44
- <DropdownMenuSeparator />
45
- <DropdownMenuSub>
46
- <DropdownMenuSubTrigger>More Options</DropdownMenuSubTrigger>
47
- <DropdownMenuSubContent>
48
- <DropdownMenuItem>Export</DropdownMenuItem>
49
- </DropdownMenuSubContent>
50
- </DropdownMenuSub>
51
- </DropdownMenuContent>
52
- </DropdownMenu>
53
- ```
54
-
55
- **Componentes**: 14 (Menu, Trigger, Content, Item, CheckboxItem, RadioGroup, RadioItem, Label, Separator, Shortcut, Sub, SubTrigger, SubContent, Group, Portal)
56
-
57
- ## Props Principales
58
-
59
- ### DropdownMenu (Root)
60
-
61
- | Prop | Tipo | Default | Descripción |
62
- | -------------- | ------------------------- | ------- | -------------------------------------------------- |
63
- | `open` | `boolean` | - | Estado controlado del menú |
64
- | `onOpenChange` | `(open: boolean) => void` | - | Callback cuando cambia estado |
65
- | `defaultOpen` | `boolean` | `false` | Estado inicial no controlado |
66
- | `modal` | `boolean` | `true` | Comportamiento modal (bloquea interacción externa) |
67
-
68
- ### DropdownMenuTrigger
69
-
70
- | Prop | Tipo | Descripción |
71
- | ----------- | --------- | -------------------------------------------------- |
72
- | `asChild` | `boolean` | Pasa props al child en lugar de renderizar wrapper |
73
- | `disabled` | `boolean` | Desactiva el trigger |
74
- | `className` | `string` | Clases CSS adicionales |
75
-
76
- ### DropdownMenuContent
77
-
78
- | Prop | Tipo | Default | Descripción |
79
- | ---------------------- | ---------------------------------------- | ---------- | --------------------------------------------- |
80
- | `align` | `"start" \| "center" \| "end"` | `"center"` | Alineamiento horizontal respecto al trigger |
81
- | `side` | `"top" \| "right" \| "bottom" \| "left"` | `"bottom"` | Lado donde aparece el menú |
82
- | `sideOffset` | `number` | `4` | Distancia desde trigger (px) |
83
- | `alignOffset` | `number` | `0` | Offset del alineamiento |
84
- | `className` | `string` | - | Clases CSS adicionales |
85
- | `loop` | `boolean` | `false` | Navegación circular con arrow keys |
86
- | `onCloseAutoFocus` | `(event) => void` | - | Callback al cerrar (antes de restaurar focus) |
87
- | `onEscapeKeyDown` | `(event) => void` | - | Callback al presionar Escape |
88
- | `onPointerDownOutside` | `(event) => void` | - | Callback al hacer clic fuera |
89
-
90
- **Estilos default**: `min-w-[8rem]`, `max-h-(--radix-dropdown-menu-content-available-height)`, scroll automático, `z-50`
91
-
92
- ### DropdownMenuItem
93
-
94
- | Prop | Tipo | Default | Descripción |
95
- | ----------- | ---------------------------- | ----------- | --------------------------------------- |
96
- | `onSelect` | `(event) => void` | - | Callback al seleccionar (click o Enter) |
97
- | `disabled` | `boolean` | `false` | Desactiva el item |
98
- | `inset` | `boolean` | `false` | Agrega padding izquierdo (`pl-8`) |
99
- | `variant` | `"default" \| "destructive"` | `"default"` | Estilo del item |
100
- | `className` | `string` | - | Clases CSS adicionales |
101
-
102
- **Variantes**:
103
-
104
- - `default`: `focus:bg-accent`, `focus:text-accent-foreground`
105
- - `destructive`: `text-destructive`, `focus:bg-destructive/10`, `focus:text-destructive`
106
-
107
- ### DropdownMenuCheckboxItem
108
-
109
- | Prop | Tipo | Descripción |
110
- | ----------------- | ---------------------------- | -------------------------- |
111
- | `checked` | `boolean \| "indeterminate"` | Estado del checkbox |
112
- | `onCheckedChange` | `(checked: boolean) => void` | Callback al cambiar estado |
113
- | `disabled` | `boolean` | Desactiva el item |
114
- | `className` | `string` | Clases CSS adicionales |
115
-
116
- **Indicador**: CheckIcon automático cuando `checked={true}`, posicionado con `pl-8`
117
-
118
- ### DropdownMenuRadioGroup
119
-
120
- | Prop | Tipo | Descripción |
121
- | --------------- | ------------------------- | ------------------------- |
122
- | `value` | `string` | Valor seleccionado |
123
- | `onValueChange` | `(value: string) => void` | Callback al cambiar valor |
124
-
125
- ### DropdownMenuRadioItem
126
-
127
- | Prop | Tipo | Descripción |
128
- | ----------- | --------- | ---------------------- |
129
- | `value` | `string` | Valor único del item |
130
- | `disabled` | `boolean` | Desactiva el item |
131
- | `className` | `string` | Clases CSS adicionales |
132
-
133
- **Indicador**: CircleIcon automático cuando seleccionado, posicionado con `pl-8`
134
-
135
- ### DropdownMenuSub
136
-
137
- | Prop | Tipo | Descripción |
138
- | -------------- | ------------------------- | ----------------------------- |
139
- | `open` | `boolean` | Estado controlado del submenú |
140
- | `onOpenChange` | `(open: boolean) => void` | Callback al cambiar estado |
141
-
142
- ### DropdownMenuSubTrigger
143
-
144
- | Prop | Tipo | Descripción |
145
- | ----------- | --------- | -------------------------- |
146
- | `disabled` | `boolean` | Desactiva el trigger |
147
- | `inset` | `boolean` | Padding izquierdo (`pl-8`) |
148
- | `className` | `string` | Clases CSS adicionales |
149
-
150
- **Indicador**: ChevronRightIcon automático con `ml-auto`
151
-
152
- ### DropdownMenuLabel
153
-
154
- | Prop | Tipo | Descripción |
155
- | ----------- | --------- | -------------------------- |
156
- | `inset` | `boolean` | Padding izquierdo (`pl-8`) |
157
- | `className` | `string` | Clases CSS adicionales |
158
-
159
- **Estilos**: `font-medium`, `text-sm`
160
-
161
- ### DropdownMenuShortcut
162
-
163
- | Prop | Tipo | Descripción |
164
- | ----------- | -------- | ---------------------- |
165
- | `className` | `string` | Clases CSS adicionales |
166
-
167
- **Estilos**: `text-muted-foreground`, `text-xs`, `tracking-widest`, `ml-auto`
168
-
169
- ## Patrones de Uso
170
-
171
- ### Básico
172
-
173
- ```tsx
174
- <DropdownMenu>
175
- <DropdownMenuTrigger>Open</DropdownMenuTrigger>
176
- <DropdownMenuContent>
177
- <DropdownMenuLabel>My Account</DropdownMenuLabel>
178
- <DropdownMenuSeparator />
179
- <DropdownMenuItem>Profile</DropdownMenuItem>
180
- <DropdownMenuItem>Billing</DropdownMenuItem>
181
- <DropdownMenuItem>Settings</DropdownMenuItem>
182
- </DropdownMenuContent>
183
- </DropdownMenu>
184
- ```
185
-
186
- ### Con Trigger Custom (asChild)
187
-
188
- ```tsx
189
- <DropdownMenu>
190
- <DropdownMenuTrigger asChild>
191
- <Button variant="outline">Open Menu</Button>
192
- </DropdownMenuTrigger>
193
- <DropdownMenuContent>
194
- <DropdownMenuItem>Profile</DropdownMenuItem>
195
- <DropdownMenuItem>Settings</DropdownMenuItem>
196
- </DropdownMenuContent>
197
- </DropdownMenu>
198
- ```
199
-
200
- ### Con Iconos y Shortcuts
201
-
202
- ```tsx
203
- import { Icon } from "@adamosuiteservices/ui/icon";
204
-
205
- <DropdownMenu>
206
- <DropdownMenuTrigger asChild>
207
- <Button variant="outline">Open</Button>
208
- </DropdownMenuTrigger>
209
- <DropdownMenuContent className="w-56" align="start">
210
- <DropdownMenuLabel>My Account</DropdownMenuLabel>
211
- <DropdownMenuSeparator />
212
- <DropdownMenuItem>
213
- <Icon symbol="person" />
214
- Profile
215
- <DropdownMenuShortcut>⇧⌘P</DropdownMenuShortcut>
216
- </DropdownMenuItem>
217
- <DropdownMenuItem>
218
- <Icon symbol="credit_card" />
219
- Billing
220
- <DropdownMenuShortcut>⌘B</DropdownMenuShortcut>
221
- </DropdownMenuItem>
222
- <DropdownMenuItem>
223
- <Icon symbol="settings" />
224
- Settings
225
- <DropdownMenuShortcut>⌘S</DropdownMenuShortcut>
226
- </DropdownMenuItem>
227
- <DropdownMenuSeparator />
228
- <DropdownMenuItem>
229
- <Icon symbol="logout" />
230
- Log out
231
- <DropdownMenuShortcut>⇧⌘Q</DropdownMenuShortcut>
232
- </DropdownMenuItem>
233
- </DropdownMenuContent>
234
- </DropdownMenu>;
235
- ```
236
-
237
- **Nota**: SVGs automáticamente `size-4`, `text-muted-foreground` (excepto en variant destructive)
238
-
239
- ### Con Checkboxes
240
-
241
- ```tsx
242
- import { useState } from "react";
243
-
244
- function ViewOptions() {
245
- const [showStatusBar, setShowStatusBar] = useState(true);
246
- const [showActivityBar, setShowActivityBar] = useState(false);
247
- const [showPanel, setShowPanel] = useState(false);
248
-
249
- return (
250
- <DropdownMenu>
251
- <DropdownMenuTrigger asChild>
252
- <Button variant="outline">View Options</Button>
253
- </DropdownMenuTrigger>
254
- <DropdownMenuContent className="w-56">
255
- <DropdownMenuLabel>Appearance</DropdownMenuLabel>
256
- <DropdownMenuSeparator />
257
- <DropdownMenuCheckboxItem
258
- checked={showStatusBar}
259
- onCheckedChange={setShowStatusBar}
260
- >
261
- Status Bar
262
- </DropdownMenuCheckboxItem>
263
- <DropdownMenuCheckboxItem
264
- checked={showActivityBar}
265
- onCheckedChange={setShowActivityBar}
266
- >
267
- Activity Bar
268
- </DropdownMenuCheckboxItem>
269
- <DropdownMenuCheckboxItem
270
- checked={showPanel}
271
- onCheckedChange={setShowPanel}
272
- >
273
- Panel
274
- </DropdownMenuCheckboxItem>
275
- </DropdownMenuContent>
276
- </DropdownMenu>
277
- );
278
- }
279
- ```
280
-
281
- ### Con Radio Group
282
-
283
- ```tsx
284
- import { useState } from "react";
285
-
286
- function Settings() {
287
- const [position, setPosition] = useState("bottom");
288
-
289
- return (
290
- <DropdownMenu>
291
- <DropdownMenuTrigger asChild>
292
- <Button variant="outline">Panel Position</Button>
293
- </DropdownMenuTrigger>
294
- <DropdownMenuContent className="w-56">
295
- <DropdownMenuLabel>Panel Position</DropdownMenuLabel>
296
- <DropdownMenuSeparator />
297
- <DropdownMenuRadioGroup value={position} onValueChange={setPosition}>
298
- <DropdownMenuRadioItem value="top">Top</DropdownMenuRadioItem>
299
- <DropdownMenuRadioItem value="bottom">Bottom</DropdownMenuRadioItem>
300
- <DropdownMenuRadioItem value="right">Right</DropdownMenuRadioItem>
301
- </DropdownMenuRadioGroup>
302
- </DropdownMenuContent>
303
- </DropdownMenu>
304
- );
305
- }
306
- ```
307
-
308
- ### Con Submenú
309
-
310
- ```tsx
311
- import { Icon } from "@adamosuiteservices/ui/icon";
312
-
313
- <DropdownMenu>
314
- <DropdownMenuTrigger asChild>
315
- <Button variant="outline">Options</Button>
316
- </DropdownMenuTrigger>
317
- <DropdownMenuContent>
318
- <DropdownMenuItem>
319
- <Icon symbol="group" />
320
- Team
321
- </DropdownMenuItem>
322
- <DropdownMenuSub>
323
- <DropdownMenuSubTrigger>
324
- <Icon symbol="person" />
325
- Invite users
326
- </DropdownMenuSubTrigger>
327
- <DropdownMenuPortal>
328
- <DropdownMenuSubContent>
329
- <DropdownMenuItem>
330
- <Icon symbol="mail" />
331
- Email
332
- </DropdownMenuItem>
333
- <DropdownMenuItem>
334
- <Icon symbol="chat_bubble" />
335
- Message
336
- </DropdownMenuItem>
337
- <DropdownMenuSeparator />
338
- <DropdownMenuItem>
339
- <Icon symbol="add" />
340
- More...
341
- </DropdownMenuItem>
342
- </DropdownMenuSubContent>
343
- </DropdownMenuPortal>
344
- </DropdownMenuSub>
345
- </DropdownMenuContent>
346
- </DropdownMenu>;
347
- ```
348
-
349
- ### User Menu (Avatar)
350
-
351
- ```tsx
352
- import { Icon } from "@adamosuiteservices/ui/icon";
353
-
354
- <DropdownMenu>
355
- <DropdownMenuTrigger asChild>
356
- <Button variant="ghost" className="h-8 w-8 rounded-full">
357
- <Icon symbol="person" />
358
- </Button>
359
- </DropdownMenuTrigger>
360
- <DropdownMenuContent className="w-56" align="end" forceMount>
361
- <DropdownMenuLabel className="font-normal">
362
- <div className="flex flex-col space-y-1">
363
- <p className="text-sm font-medium leading-none">John Doe</p>
364
- <p className="text-xs leading-none text-muted-foreground">
365
- john.doe@example.com
366
- </p>
367
- </div>
368
- </DropdownMenuLabel>
369
- <DropdownMenuSeparator />
370
- <DropdownMenuItem>
371
- <Icon symbol="person" />
372
- Profile
373
- <DropdownMenuShortcut>⇧⌘P</DropdownMenuShortcut>
374
- </DropdownMenuItem>
375
- <DropdownMenuItem>
376
- <Icon symbol="credit_card" />
377
- Billing
378
- <DropdownMenuShortcut>⌘B</DropdownMenuShortcut>
379
- </DropdownMenuItem>
380
- <DropdownMenuItem>
381
- <Icon symbol="settings" />
382
- Settings
383
- <DropdownMenuShortcut>⌘S</DropdownMenuShortcut>
384
- </DropdownMenuItem>
385
- <DropdownMenuSeparator />
386
- <DropdownMenuItem>
387
- <Icon symbol="logout" />
388
- Log out
389
- <DropdownMenuShortcut>⇧⌘Q</DropdownMenuShortcut>
390
- </DropdownMenuItem>
391
- </DropdownMenuContent>
392
- </DropdownMenu>;
393
- ```
394
-
395
- ### File Actions (More Menu)
396
-
397
- ```tsx
398
- import {
399
- MoreHorizontalIcon,
400
- import { Icon } from "@adamosuiteservices/ui/icon";
401
-
402
- <DropdownMenu>
403
- <DropdownMenuTrigger asChild>
404
- <Button variant="ghost" size="sm">
405
- <Icon symbol="more_horiz" />
406
- </Button>
407
- </DropdownMenuTrigger>
408
- <DropdownMenuContent align="end">
409
- <DropdownMenuItem>
410
- <Icon symbol="open_in_new" />
411
- Open
412
- </DropdownMenuItem>
413
- <DropdownMenuItem>
414
- <Icon symbol="content_copy" />
415
- Copy Link
416
- </DropdownMenuItem>
417
- <DropdownMenuItem>
418
- <Icon symbol="share" />
419
- Share
420
- </DropdownMenuItem>
421
- <DropdownMenuSeparator />
422
- <DropdownMenuItem>
423
- <Icon symbol="download" />
424
- Download
425
- </DropdownMenuItem>
426
- <DropdownMenuItem>
427
- <Icon symbol="edit" />
428
- Rename
429
- </DropdownMenuItem>
430
- <DropdownMenuSeparator />
431
- <DropdownMenuItem variant="destructive">
432
- <Icon symbol="delete" />
433
- Delete
434
- </DropdownMenuItem>
435
- </DropdownMenuContent>
436
- </DropdownMenu>;
437
- ```
438
-
439
- ### Simple Actions con Chevron
440
-
441
- ```tsx
442
- import {
443
- ChevronDownIcon,
444
- EditIcon,
445
- import { Icon } from "@adamosuiteservices/ui/icon";
446
-
447
- <DropdownMenu>
448
- <DropdownMenuTrigger asChild>
449
- <Button variant="outline">
450
- Actions
451
- <Icon symbol="expand_more" />
452
- </Button>
453
- </DropdownMenuTrigger>
454
- <DropdownMenuContent>
455
- <DropdownMenuItem>
456
- <Icon symbol="edit" />
457
- Edit
458
- </DropdownMenuItem>
459
- <DropdownMenuItem>
460
- <Icon symbol="content_copy" />
461
- Duplicate
462
- </DropdownMenuItem>
463
- <DropdownMenuItem>
464
- <Icon symbol="share" />
465
- Share
466
- </DropdownMenuItem>
467
- <DropdownMenuSeparator />
468
- <DropdownMenuItem variant="destructive">
469
- <Icon symbol="delete" />
470
- Delete
471
- </DropdownMenuItem>
472
- </DropdownMenuContent>
473
- </DropdownMenu>;
474
- ```
475
-
476
- ### Project Menu (Nested)
477
-
478
- ```tsx
479
- import {
480
- FolderIcon,
481
- EditIcon,
482
- ShareIcon,
483
- CopyIcon,
484
- UsersIcon,
485
- import { Icon } from "@adamosuiteservices/ui/icon";
486
-
487
- <DropdownMenu>
488
- <DropdownMenuTrigger asChild>
489
- <Button variant="outline">
490
- <Icon symbol="folder" />
491
- My Project
492
- <Icon symbol="expand_more" />
493
- </Button>
494
- </DropdownMenuTrigger>
495
- <DropdownMenuContent className="w-56">
496
- <DropdownMenuLabel>Project Actions</DropdownMenuLabel>
497
- <DropdownMenuSeparator />
498
- <DropdownMenuItem>
499
- <Icon symbol="edit" />
500
- Edit Project
501
- </DropdownMenuItem>
502
- <DropdownMenuItem>
503
- <Icon symbol="share" />
504
- Share Project
505
- </DropdownMenuItem>
506
- <DropdownMenuItem>
507
- <Icon symbol="content_copy" />
508
- Duplicate Project
509
- </DropdownMenuItem>
510
- <DropdownMenuSeparator />
511
- <DropdownMenuSub>
512
- <DropdownMenuSubTrigger>
513
- <Icon symbol="group" />
514
- Manage Access
515
- </DropdownMenuSubTrigger>
516
- <DropdownMenuPortal>
517
- <DropdownMenuSubContent>
518
- <DropdownMenuItem>
519
- <Icon symbol="person_add" />
520
- Add Member
521
- </DropdownMenuItem>
522
- <DropdownMenuItem>
523
- <Icon symbol="admin_panel_settings" />
524
- Permissions
525
- </DropdownMenuItem>
526
- <DropdownMenuSeparator />
527
- <DropdownMenuItem>
528
- <Icon symbol="group" />
529
- View Members
530
- </DropdownMenuItem>
531
- </DropdownMenuSubContent>
532
- </DropdownMenuPortal>
533
- </DropdownMenuSub>
534
- <DropdownMenuSeparator />
535
- <DropdownMenuItem>
536
- <Icon symbol="download" />
537
- Export
538
- </DropdownMenuItem>
539
- <DropdownMenuItem>
540
- <Icon symbol="settings" />
541
- Settings
542
- </DropdownMenuItem>
543
- <DropdownMenuSeparator />
544
- <DropdownMenuItem variant="destructive">
545
- <Icon symbol="delete" />
546
- Delete Project
547
- </DropdownMenuItem>
548
- </DropdownMenuContent>
549
- </DropdownMenu>;
550
- ```
551
-
552
- ### Items Deshabilitados
553
-
554
- ```tsx
555
- <DropdownMenu>
556
- <DropdownMenuTrigger asChild>
557
- <Button variant="outline">Options</Button>
558
- </DropdownMenuTrigger>
559
- <DropdownMenuContent>
560
- <DropdownMenuItem>Available Action</DropdownMenuItem>
561
- <DropdownMenuItem disabled>Premium Feature</DropdownMenuItem>
562
- <DropdownMenuItem>Another Action</DropdownMenuItem>
563
- <DropdownMenuSeparator />
564
- <DropdownMenuItem disabled>Coming Soon</DropdownMenuItem>
565
- <DropdownMenuItem variant="destructive" disabled>
566
- No Permission
567
- </DropdownMenuItem>
568
- </DropdownMenuContent>
569
- </DropdownMenu>
570
- ```
571
-
572
- **Estilos disabled**: `opacity-50`, `pointer-events-none`, `data-[disabled=true]`
573
-
574
- ### Controlado
575
-
576
- ```tsx
577
- import { useState } from "react";
578
-
579
- function App() {
580
- const [open, setOpen] = useState(false);
581
-
582
- return (
583
- <DropdownMenu open={open} onOpenChange={setOpen}>
584
- <DropdownMenuTrigger asChild>
585
- <Button>Menu</Button>
586
- </DropdownMenuTrigger>
587
- <DropdownMenuContent>
588
- <DropdownMenuItem onClick={() => setOpen(false)}>
589
- Action (closes menu)
590
- </DropdownMenuItem>
591
- </DropdownMenuContent>
592
- </DropdownMenu>
593
- );
594
- }
595
- ```
596
-
597
- ## Casos de Uso Comunes
598
-
599
- **User menus**: Avatar menu con profile, settings, logout
600
- **Action menus**: File actions, row actions, more options
601
- **Navigation**: Nested navigation menus
602
- **Filters/Options**: View settings, panel positions
603
- **Context actions**: Edit, share, delete operations
604
- **Bulk actions**: Select multiple items, apply actions
605
-
606
- ## Estados y Data Attributes
607
-
608
- ### DropdownMenuItem States
609
-
610
- - **Focus**: `data-[highlighted]` `bg-accent`, `text-accent-foreground`
611
- - **Disabled**: `data-[disabled=true]` `opacity-50`, `pointer-events-none`
612
- - **Destructive**: `data-[variant=destructive]` `text-destructive`
613
-
614
- ### DropdownMenuSubTrigger States
615
-
616
- - **Open**: `data-[state=open]` → `bg-accent`, `text-accent-foreground`
617
-
618
- ### Content Animations
619
-
620
- - **Opening**: `data-[state=open]` → `animate-in`, `fade-in-0`, `zoom-in-95`
621
- - **Closing**: `data-[state=closed]` → `animate-out`, `fade-out-0`, `zoom-out-95`
622
- - **Slide in**: `data-[side=top|right|bottom|left]` → direccional
623
-
624
- ## Navegación por Teclado
625
-
626
- - **Arrow Up/Down**: Navega entre items
627
- - ✅ **Arrow Right**: Abre submenú (si disponible)
628
- - ✅ **Arrow Left**: Cierra submenú
629
- - ✅ **Enter/Space**: Activa item enfocado
630
- - **Escape**: Cierra menú
631
- - **Tab**: Mueve focus fuera del menú
632
- - **Home/End**: Primer/último item (si `loop={true}`)
633
- - ✅ **Type ahead**: Busca item por primera letra
634
-
635
- ## Accesibilidad
636
-
637
- - **ARIA**: `role="menu"` en content, `role="menuitem"` en items
638
- - **Keyboard navigation**: Navegación completa por teclado
639
- - ✅ **Focus management**: Focus visible y restaurado correctamente
640
- - **Screen readers**: Anuncia items, estado de checkboxes/radios, disabled
641
- - ✅ **Disabled items**: `aria-disabled="true"` en items deshabilitados
642
- - **Submenus**: `aria-haspopup="menu"` en SubTriggers
643
- - ✅ **Checked state**: `aria-checked` en CheckboxItem y RadioItem
644
-
645
- ## Notas de Implementación
646
-
647
- - **Basado en Radix UI**: `@radix-ui/react-dropdown-menu`
648
- - **Click to open**: Se activa con click (diferente de ContextMenu que usa right-click)
649
- - **Auto positioning**: Portal con posicionamiento automático
650
- - **Collision detection**: Evita salirse del viewport
651
- - **Auto-close**: Cierra al seleccionar item (excepto checkboxes/radios)
652
- - **Focus trap**: Mientras está abierto, focus queda dentro del menú
653
- - **Portal rendering**: Content se renderiza en `document.body` por defecto
654
- - **SVG auto-sizing**: Iconos automáticamente `size-4` y color muted
655
- - **Inset support**: `inset` prop agrega `pl-8` para alinear con items con iconos
656
- - **ChevronRightIcon**: Incluido automáticamente en SubTriggers
657
- - **CheckIcon/CircleIcon**: Incluidos automáticamente en CheckboxItem/RadioItem
658
- - **Variant destructive**: SVGs ignoran color muted y usan `text-destructive`
659
- - **Default sideOffset**: 4px de distancia desde trigger
660
-
661
- ## Posicionamiento
662
-
663
- Control completo del posicionamiento del menú:
664
-
665
- ```tsx
666
- <DropdownMenuContent
667
- align="end" // "start" | "center" | "end"
668
- side="bottom" // "top" | "right" | "bottom" | "left"
669
- sideOffset={8} // distancia desde trigger
670
- alignOffset={-4} // offset horizontal
671
- className="w-64" // ancho custom
672
- >
673
- {/* ... */}
674
- </DropdownMenuContent>
675
- ```
676
-
677
- **Ejemplos comunes**:
678
-
679
- - User menu (top-right avatar): `align="end"`
680
- - Actions menu (more button): `align="end"`
681
- - Navigation menu: `align="start"`
682
-
683
- ## Troubleshooting
684
-
685
- **Menú no aparece**: Verifica que DropdownMenuTrigger tenga `asChild` si usas Button custom
686
- **Items no responden a click**: Usa `onSelect` en lugar de `onClick`
687
- **Iconos mal alineados**: Usa `inset` en items sin iconos cuando otros sí tienen
688
- **Menú no cierra al seleccionar**: CheckboxItems y RadioItems no cierran automáticamente (comportamiento esperado)
689
- **Submenú no abre**: Verifica que estés usando `DropdownMenuSub` + `DropdownMenuSubTrigger` + `DropdownMenuSubContent`
690
- **Shortcuts no ejecutan**: `DropdownMenuShortcut` es solo visual, implementa lógica en `onSelect`
691
- **Focus se pierde al cerrar**: Usa `onCloseAutoFocus` para control custom del focus
692
- **Posición incorrecta**: Verifica `align` y `side` props, ajusta con `sideOffset` y `alignOffset`
693
- **Trigger no muestra estado**: Radix no cambia apariencia del trigger automáticamente, usa estado controlado
694
-
695
- ## Diferencias con ContextMenu
696
-
697
- | Aspecto | DropdownMenu | ContextMenu |
698
- | -------------------- | --------------------------- | --------------------- |
699
- | **Activación** | Click izquierdo | Click derecho |
700
- | **Trigger** | Botón/elemento visible | Área target |
701
- | **Uso común** | Menús de acción, navegación | Acciones contextuales |
702
- | **Indicador visual** | Botón con chevron | Sin indicador |
703
- | **Posicionamiento** | Relativo al trigger | Posición del cursor |
704
-
705
- ## Referencias
706
-
707
- - **Radix UI Dropdown Menu**: https://www.radix-ui.com/primitives/docs/components/dropdown-menu
708
- - **shadcn/ui Dropdown Menu**: https://ui.shadcn.com/docs/components/dropdown-menu
709
- - **ARIA Menu Pattern**: https://www.w3.org/WAI/ARIA/apg/patterns/menu-button/
1
+ # Dropdown Menu
2
+
3
+ Menú desplegable activado por click, basado en Radix UI. Soporta submenús, checkboxes, radio groups, separadores, y shortcuts visuales. Similar a ContextMenu pero activado con click en lugar de click derecho.
4
+
5
+ ## Importación
6
+
7
+ ```tsx
8
+ import {
9
+ DropdownMenu,
10
+ DropdownMenuTrigger,
11
+ DropdownMenuContent,
12
+ DropdownMenuItem,
13
+ DropdownMenuCheckboxItem,
14
+ DropdownMenuRadioGroup,
15
+ DropdownMenuRadioItem,
16
+ DropdownMenuLabel,
17
+ DropdownMenuSeparator,
18
+ DropdownMenuShortcut,
19
+ DropdownMenuSub,
20
+ DropdownMenuSubContent,
21
+ DropdownMenuSubTrigger,
22
+ DropdownMenuGroup,
23
+ DropdownMenuPortal,
24
+ } from "@adamosuiteservices/ui/dropdown-menu";
25
+ ```
26
+
27
+ ## Anatomía
28
+
29
+ ```tsx
30
+ <DropdownMenu>
31
+ <DropdownMenuTrigger asChild>
32
+ <Button>Open Menu</Button>
33
+ </DropdownMenuTrigger>
34
+ <DropdownMenuContent>
35
+ <DropdownMenuLabel>My Account</DropdownMenuLabel>
36
+ <DropdownMenuSeparator />
37
+ <DropdownMenuItem>
38
+ Profile
39
+ <DropdownMenuShortcut>⇧⌘P</DropdownMenuShortcut>
40
+ </DropdownMenuItem>
41
+ <DropdownMenuCheckboxItem checked={showPanel}>
42
+ Show Panel
43
+ </DropdownMenuCheckboxItem>
44
+ <DropdownMenuSeparator />
45
+ <DropdownMenuSub>
46
+ <DropdownMenuSubTrigger>More Options</DropdownMenuSubTrigger>
47
+ <DropdownMenuSubContent>
48
+ <DropdownMenuItem>Export</DropdownMenuItem>
49
+ </DropdownMenuSubContent>
50
+ </DropdownMenuSub>
51
+ </DropdownMenuContent>
52
+ </DropdownMenu>
53
+ ```
54
+
55
+ **Componentes**: 14 (Menu, Trigger, Content, Item, CheckboxItem, RadioGroup, RadioItem, Label, Separator, Shortcut, Sub, SubTrigger, SubContent, Group, Portal)
56
+
57
+ ## Props Principales
58
+
59
+ ### DropdownMenu (Root)
60
+
61
+ | Prop | Tipo | Default | Descripción |
62
+ | -------------- | ------------------------- | ------- | -------------------------------------------------- |
63
+ | `open` | `boolean` | - | Estado controlado del menú |
64
+ | `onOpenChange` | `(open: boolean) => void` | - | Callback cuando cambia estado |
65
+ | `defaultOpen` | `boolean` | `false` | Estado inicial no controlado |
66
+ | `modal` | `boolean` | `true` | Comportamiento modal (bloquea interacción externa) |
67
+
68
+ ### DropdownMenuTrigger
69
+
70
+ | Prop | Tipo | Descripción |
71
+ | ----------- | --------- | -------------------------------------------------- |
72
+ | `asChild` | `boolean` | Pasa props al child en lugar de renderizar wrapper |
73
+ | `disabled` | `boolean` | Desactiva el trigger |
74
+ | `className` | `string` | Clases CSS adicionales |
75
+
76
+ ### DropdownMenuContent
77
+
78
+ | Prop | Tipo | Default | Descripción |
79
+ | ---------------------- | ---------------------------------------- | ---------- | --------------------------------------------- |
80
+ | `align` | `"start" \| "center" \| "end"` | `"center"` | Alineamiento horizontal respecto al trigger |
81
+ | `side` | `"top" \| "right" \| "bottom" \| "left"` | `"bottom"` | Lado donde aparece el menú |
82
+ | `sideOffset` | `number` | `4` | Distancia desde trigger (px) |
83
+ | `alignOffset` | `number` | `0` | Offset del alineamiento |
84
+ | `className` | `string` | - | Clases CSS adicionales |
85
+ | `loop` | `boolean` | `false` | Navegación circular con arrow keys |
86
+ | `onCloseAutoFocus` | `(event) => void` | - | Callback al cerrar (antes de restaurar focus) |
87
+ | `onEscapeKeyDown` | `(event) => void` | - | Callback al presionar Escape |
88
+ | `onPointerDownOutside` | `(event) => void` | - | Callback al hacer clic fuera |
89
+
90
+ **Estilos default**: `min-w-[8rem]`, `max-h-(--radix-dropdown-menu-content-available-height)`, scroll automático, `z-50`
91
+
92
+ ### DropdownMenuItem
93
+
94
+ | Prop | Tipo | Default | Descripción |
95
+ | ----------- | ---------------------------- | ----------- | --------------------------------------- |
96
+ | `onSelect` | `(event) => void` | - | Callback al seleccionar (click o Enter) |
97
+ | `disabled` | `boolean` | `false` | Desactiva el item |
98
+ | `inset` | `boolean` | `false` | Agrega padding izquierdo (`pl-8`) |
99
+ | `variant` | `"default" \| "destructive"` | `"default"` | Estilo del item |
100
+ | `className` | `string` | - | Clases CSS adicionales |
101
+
102
+ **Variantes**:
103
+
104
+ - `default`: `focus:bg-accent`, `focus:text-accent-foreground`
105
+ - `destructive`: `text-destructive`, `focus:bg-destructive/10`, `focus:text-destructive`
106
+
107
+ ### DropdownMenuCheckboxItem
108
+
109
+ | Prop | Tipo | Descripción |
110
+ | ----------------- | ---------------------------- | -------------------------- |
111
+ | `checked` | `boolean \| "indeterminate"` | Estado del checkbox |
112
+ | `onCheckedChange` | `(checked: boolean) => void` | Callback al cambiar estado |
113
+ | `disabled` | `boolean` | Desactiva el item |
114
+ | `className` | `string` | Clases CSS adicionales |
115
+
116
+ **Indicador**: CheckIcon automático cuando `checked={true}`, posicionado con `pl-8`
117
+
118
+ ### DropdownMenuRadioGroup
119
+
120
+ | Prop | Tipo | Descripción |
121
+ | --------------- | ------------------------- | ------------------------- |
122
+ | `value` | `string` | Valor seleccionado |
123
+ | `onValueChange` | `(value: string) => void` | Callback al cambiar valor |
124
+
125
+ ### DropdownMenuRadioItem
126
+
127
+ | Prop | Tipo | Descripción |
128
+ | ----------- | --------- | ---------------------- |
129
+ | `value` | `string` | Valor único del item |
130
+ | `disabled` | `boolean` | Desactiva el item |
131
+ | `className` | `string` | Clases CSS adicionales |
132
+
133
+ **Indicador**: CircleIcon automático cuando seleccionado, posicionado con `pl-8`
134
+
135
+ ### DropdownMenuSub
136
+
137
+ | Prop | Tipo | Descripción |
138
+ | -------------- | ------------------------- | ----------------------------- |
139
+ | `open` | `boolean` | Estado controlado del submenú |
140
+ | `onOpenChange` | `(open: boolean) => void` | Callback al cambiar estado |
141
+
142
+ ### DropdownMenuSubTrigger
143
+
144
+ | Prop | Tipo | Descripción |
145
+ | ----------- | --------- | -------------------------- |
146
+ | `disabled` | `boolean` | Desactiva el trigger |
147
+ | `inset` | `boolean` | Padding izquierdo (`pl-8`) |
148
+ | `className` | `string` | Clases CSS adicionales |
149
+
150
+ **Indicador**: ChevronRightIcon automático con `ml-auto`
151
+
152
+ ### DropdownMenuLabel
153
+
154
+ | Prop | Tipo | Descripción |
155
+ | ----------- | --------- | -------------------------- |
156
+ | `inset` | `boolean` | Padding izquierdo (`pl-8`) |
157
+ | `className` | `string` | Clases CSS adicionales |
158
+
159
+ **Estilos**: `font-medium`, `text-sm`
160
+
161
+ ### DropdownMenuShortcut
162
+
163
+ | Prop | Tipo | Descripción |
164
+ | ----------- | -------- | ---------------------- |
165
+ | `className` | `string` | Clases CSS adicionales |
166
+
167
+ **Estilos**: `text-muted-foreground`, `text-xs`, `tracking-widest`, `ml-auto`
168
+
169
+ ## Patrones de Uso
170
+
171
+ ### Básico
172
+
173
+ ```tsx
174
+ <DropdownMenu>
175
+ <DropdownMenuTrigger>Open</DropdownMenuTrigger>
176
+ <DropdownMenuContent>
177
+ <DropdownMenuLabel>My Account</DropdownMenuLabel>
178
+ <DropdownMenuSeparator />
179
+ <DropdownMenuItem>Profile</DropdownMenuItem>
180
+ <DropdownMenuItem>Billing</DropdownMenuItem>
181
+ <DropdownMenuItem>Settings</DropdownMenuItem>
182
+ </DropdownMenuContent>
183
+ </DropdownMenu>
184
+ ```
185
+
186
+ ### Con Trigger Custom (asChild)
187
+
188
+ ```tsx
189
+ <DropdownMenu>
190
+ <DropdownMenuTrigger asChild>
191
+ <Button variant="outline">Open Menu</Button>
192
+ </DropdownMenuTrigger>
193
+ <DropdownMenuContent>
194
+ <DropdownMenuItem>Profile</DropdownMenuItem>
195
+ <DropdownMenuItem>Settings</DropdownMenuItem>
196
+ </DropdownMenuContent>
197
+ </DropdownMenu>
198
+ ```
199
+
200
+ ### Con Iconos y Shortcuts
201
+
202
+ ```tsx
203
+ import { Icon } from "@adamosuiteservices/ui/icon";
204
+
205
+ <DropdownMenu>
206
+ <DropdownMenuTrigger asChild>
207
+ <Button variant="outline">Open</Button>
208
+ </DropdownMenuTrigger>
209
+ <DropdownMenuContent className="w-56" align="start">
210
+ <DropdownMenuLabel>My Account</DropdownMenuLabel>
211
+ <DropdownMenuSeparator />
212
+ <DropdownMenuItem>
213
+ <Icon symbol="person" />
214
+ Profile
215
+ <DropdownMenuShortcut>⇧⌘P</DropdownMenuShortcut>
216
+ </DropdownMenuItem>
217
+ <DropdownMenuItem>
218
+ <Icon symbol="credit_card" />
219
+ Billing
220
+ <DropdownMenuShortcut>⌘B</DropdownMenuShortcut>
221
+ </DropdownMenuItem>
222
+ <DropdownMenuItem>
223
+ <Icon symbol="settings" />
224
+ Settings
225
+ <DropdownMenuShortcut>⌘S</DropdownMenuShortcut>
226
+ </DropdownMenuItem>
227
+ <DropdownMenuSeparator />
228
+ <DropdownMenuItem>
229
+ <Icon symbol="logout" />
230
+ Log out
231
+ <DropdownMenuShortcut>⇧⌘Q</DropdownMenuShortcut>
232
+ </DropdownMenuItem>
233
+ </DropdownMenuContent>
234
+ </DropdownMenu>;
235
+ ```
236
+
237
+ **Nota**: SVGs automáticamente `size-4`, `text-muted-foreground` (excepto en variant destructive)
238
+
239
+ ### Con Checkboxes
240
+
241
+ ```tsx
242
+ import { useState } from "react";
243
+
244
+ function ViewOptions() {
245
+ const [showStatusBar, setShowStatusBar] = useState(true);
246
+ const [showActivityBar, setShowActivityBar] = useState(false);
247
+ const [showPanel, setShowPanel] = useState(false);
248
+
249
+ return (
250
+ <DropdownMenu>
251
+ <DropdownMenuTrigger asChild>
252
+ <Button variant="outline">View Options</Button>
253
+ </DropdownMenuTrigger>
254
+ <DropdownMenuContent className="w-56">
255
+ <DropdownMenuLabel>Appearance</DropdownMenuLabel>
256
+ <DropdownMenuSeparator />
257
+ <DropdownMenuCheckboxItem
258
+ checked={showStatusBar}
259
+ onCheckedChange={setShowStatusBar}
260
+ >
261
+ Status Bar
262
+ </DropdownMenuCheckboxItem>
263
+ <DropdownMenuCheckboxItem
264
+ checked={showActivityBar}
265
+ onCheckedChange={setShowActivityBar}
266
+ >
267
+ Activity Bar
268
+ </DropdownMenuCheckboxItem>
269
+ <DropdownMenuCheckboxItem
270
+ checked={showPanel}
271
+ onCheckedChange={setShowPanel}
272
+ >
273
+ Panel
274
+ </DropdownMenuCheckboxItem>
275
+ </DropdownMenuContent>
276
+ </DropdownMenu>
277
+ );
278
+ }
279
+ ```
280
+
281
+ ### Con Radio Group
282
+
283
+ ```tsx
284
+ import { useState } from "react";
285
+
286
+ function Settings() {
287
+ const [position, setPosition] = useState("bottom");
288
+
289
+ return (
290
+ <DropdownMenu>
291
+ <DropdownMenuTrigger asChild>
292
+ <Button variant="outline">Panel Position</Button>
293
+ </DropdownMenuTrigger>
294
+ <DropdownMenuContent className="w-56">
295
+ <DropdownMenuLabel>Panel Position</DropdownMenuLabel>
296
+ <DropdownMenuSeparator />
297
+ <DropdownMenuRadioGroup value={position} onValueChange={setPosition}>
298
+ <DropdownMenuRadioItem value="top">Top</DropdownMenuRadioItem>
299
+ <DropdownMenuRadioItem value="bottom">Bottom</DropdownMenuRadioItem>
300
+ <DropdownMenuRadioItem value="right">Right</DropdownMenuRadioItem>
301
+ </DropdownMenuRadioGroup>
302
+ </DropdownMenuContent>
303
+ </DropdownMenu>
304
+ );
305
+ }
306
+ ```
307
+
308
+ ### Con Submenú
309
+
310
+ ```tsx
311
+ import { Icon } from "@adamosuiteservices/ui/icon";
312
+
313
+ <DropdownMenu>
314
+ <DropdownMenuTrigger asChild>
315
+ <Button variant="outline">Options</Button>
316
+ </DropdownMenuTrigger>
317
+ <DropdownMenuContent>
318
+ <DropdownMenuItem>
319
+ <Icon symbol="group" />
320
+ Team
321
+ </DropdownMenuItem>
322
+ <DropdownMenuSub>
323
+ <DropdownMenuSubTrigger>
324
+ <Icon symbol="person" />
325
+ Invite users
326
+ </DropdownMenuSubTrigger>
327
+ <DropdownMenuPortal>
328
+ <DropdownMenuSubContent>
329
+ <DropdownMenuItem>
330
+ <Icon symbol="mail" />
331
+ Email
332
+ </DropdownMenuItem>
333
+ <DropdownMenuItem>
334
+ <Icon symbol="chat_bubble" />
335
+ Message
336
+ </DropdownMenuItem>
337
+ <DropdownMenuSeparator />
338
+ <DropdownMenuItem>
339
+ <Icon symbol="add" />
340
+ More...
341
+ </DropdownMenuItem>
342
+ </DropdownMenuSubContent>
343
+ </DropdownMenuPortal>
344
+ </DropdownMenuSub>
345
+ </DropdownMenuContent>
346
+ </DropdownMenu>;
347
+ ```
348
+
349
+ ### User Menu (Avatar)
350
+
351
+ ```tsx
352
+ import { Icon } from "@adamosuiteservices/ui/icon";
353
+
354
+ <DropdownMenu>
355
+ <DropdownMenuTrigger asChild>
356
+ <Button variant="ghost" className="h-8 w-8 rounded-full">
357
+ <Icon symbol="person" />
358
+ </Button>
359
+ </DropdownMenuTrigger>
360
+ <DropdownMenuContent className="w-56" align="end" forceMount>
361
+ <DropdownMenuLabel className="font-normal">
362
+ <div className="flex flex-col space-y-1">
363
+ <p className="text-sm font-medium leading-none">John Doe</p>
364
+ <p className="text-xs leading-none text-muted-foreground">
365
+ john.doe@example.com
366
+ </p>
367
+ </div>
368
+ </DropdownMenuLabel>
369
+ <DropdownMenuSeparator />
370
+ <DropdownMenuItem>
371
+ <Icon symbol="person" />
372
+ Profile
373
+ <DropdownMenuShortcut>⇧⌘P</DropdownMenuShortcut>
374
+ </DropdownMenuItem>
375
+ <DropdownMenuItem>
376
+ <Icon symbol="credit_card" />
377
+ Billing
378
+ <DropdownMenuShortcut>⌘B</DropdownMenuShortcut>
379
+ </DropdownMenuItem>
380
+ <DropdownMenuItem>
381
+ <Icon symbol="settings" />
382
+ Settings
383
+ <DropdownMenuShortcut>⌘S</DropdownMenuShortcut>
384
+ </DropdownMenuItem>
385
+ <DropdownMenuSeparator />
386
+ <DropdownMenuItem>
387
+ <Icon symbol="logout" />
388
+ Log out
389
+ <DropdownMenuShortcut>⇧⌘Q</DropdownMenuShortcut>
390
+ </DropdownMenuItem>
391
+ </DropdownMenuContent>
392
+ </DropdownMenu>;
393
+ ```
394
+
395
+ ### File Actions (More Menu)
396
+
397
+ ```tsx
398
+ import {
399
+ MoreHorizontalIcon,
400
+ import { Icon } from "@adamosuiteservices/ui/icon";
401
+
402
+ <DropdownMenu>
403
+ <DropdownMenuTrigger asChild>
404
+ <Button variant="ghost" size="sm">
405
+ <Icon symbol="more_horiz" />
406
+ </Button>
407
+ </DropdownMenuTrigger>
408
+ <DropdownMenuContent align="end">
409
+ <DropdownMenuItem>
410
+ <Icon symbol="open_in_new" />
411
+ Open
412
+ </DropdownMenuItem>
413
+ <DropdownMenuItem>
414
+ <Icon symbol="content_copy" />
415
+ Copy Link
416
+ </DropdownMenuItem>
417
+ <DropdownMenuItem>
418
+ <Icon symbol="share" />
419
+ Share
420
+ </DropdownMenuItem>
421
+ <DropdownMenuSeparator />
422
+ <DropdownMenuItem>
423
+ <Icon symbol="download" />
424
+ Download
425
+ </DropdownMenuItem>
426
+ <DropdownMenuItem>
427
+ <Icon symbol="edit" />
428
+ Rename
429
+ </DropdownMenuItem>
430
+ <DropdownMenuSeparator />
431
+ <DropdownMenuItem variant="destructive">
432
+ <Icon symbol="delete" />
433
+ Delete
434
+ </DropdownMenuItem>
435
+ </DropdownMenuContent>
436
+ </DropdownMenu>;
437
+ ```
438
+
439
+ ### Simple Actions con Chevron
440
+
441
+ ```tsx
442
+ import {
443
+ ChevronDownIcon,
444
+ EditIcon,
445
+ import { Icon } from "@adamosuiteservices/ui/icon";
446
+
447
+ <DropdownMenu>
448
+ <DropdownMenuTrigger asChild>
449
+ <Button variant="outline">
450
+ Actions
451
+ <Icon symbol="expand_more" />
452
+ </Button>
453
+ </DropdownMenuTrigger>
454
+ <DropdownMenuContent>
455
+ <DropdownMenuItem>
456
+ <Icon symbol="edit" />
457
+ Edit
458
+ </DropdownMenuItem>
459
+ <DropdownMenuItem>
460
+ <Icon symbol="content_copy" />
461
+ Duplicate
462
+ </DropdownMenuItem>
463
+ <DropdownMenuItem>
464
+ <Icon symbol="share" />
465
+ Share
466
+ </DropdownMenuItem>
467
+ <DropdownMenuSeparator />
468
+ <DropdownMenuItem variant="destructive">
469
+ <Icon symbol="delete" />
470
+ Delete
471
+ </DropdownMenuItem>
472
+ </DropdownMenuContent>
473
+ </DropdownMenu>;
474
+ ```
475
+
476
+ ### Project Menu (Nested)
477
+
478
+ ```tsx
479
+ import {
480
+ FolderIcon,
481
+ EditIcon,
482
+ ShareIcon,
483
+ CopyIcon,
484
+ UsersIcon,
485
+ import { Icon } from "@adamosuiteservices/ui/icon";
486
+
487
+ <DropdownMenu>
488
+ <DropdownMenuTrigger asChild>
489
+ <Button variant="outline">
490
+ <Icon symbol="folder" />
491
+ My Project
492
+ <Icon symbol="expand_more" />
493
+ </Button>
494
+ </DropdownMenuTrigger>
495
+ <DropdownMenuContent className="w-56">
496
+ <DropdownMenuLabel>Project Actions</DropdownMenuLabel>
497
+ <DropdownMenuSeparator />
498
+ <DropdownMenuItem>
499
+ <Icon symbol="edit" />
500
+ Edit Project
501
+ </DropdownMenuItem>
502
+ <DropdownMenuItem>
503
+ <Icon symbol="share" />
504
+ Share Project
505
+ </DropdownMenuItem>
506
+ <DropdownMenuItem>
507
+ <Icon symbol="content_copy" />
508
+ Duplicate Project
509
+ </DropdownMenuItem>
510
+ <DropdownMenuSeparator />
511
+ <DropdownMenuSub>
512
+ <DropdownMenuSubTrigger>
513
+ <Icon symbol="group" />
514
+ Manage Access
515
+ </DropdownMenuSubTrigger>
516
+ <DropdownMenuPortal>
517
+ <DropdownMenuSubContent>
518
+ <DropdownMenuItem>
519
+ <Icon symbol="person_add" />
520
+ Add Member
521
+ </DropdownMenuItem>
522
+ <DropdownMenuItem>
523
+ <Icon symbol="admin_panel_settings" />
524
+ Permissions
525
+ </DropdownMenuItem>
526
+ <DropdownMenuSeparator />
527
+ <DropdownMenuItem>
528
+ <Icon symbol="group" />
529
+ View Members
530
+ </DropdownMenuItem>
531
+ </DropdownMenuSubContent>
532
+ </DropdownMenuPortal>
533
+ </DropdownMenuSub>
534
+ <DropdownMenuSeparator />
535
+ <DropdownMenuItem>
536
+ <Icon symbol="download" />
537
+ Export
538
+ </DropdownMenuItem>
539
+ <DropdownMenuItem>
540
+ <Icon symbol="settings" />
541
+ Settings
542
+ </DropdownMenuItem>
543
+ <DropdownMenuSeparator />
544
+ <DropdownMenuItem variant="destructive">
545
+ <Icon symbol="delete" />
546
+ Delete Project
547
+ </DropdownMenuItem>
548
+ </DropdownMenuContent>
549
+ </DropdownMenu>;
550
+ ```
551
+
552
+ ### Items Deshabilitados
553
+
554
+ ```tsx
555
+ <DropdownMenu>
556
+ <DropdownMenuTrigger asChild>
557
+ <Button variant="outline">Options</Button>
558
+ </DropdownMenuTrigger>
559
+ <DropdownMenuContent>
560
+ <DropdownMenuItem>Available Action</DropdownMenuItem>
561
+ <DropdownMenuItem disabled>Premium Feature</DropdownMenuItem>
562
+ <DropdownMenuItem>Another Action</DropdownMenuItem>
563
+ <DropdownMenuSeparator />
564
+ <DropdownMenuItem disabled>Coming Soon</DropdownMenuItem>
565
+ <DropdownMenuItem variant="destructive" disabled>
566
+ No Permission
567
+ </DropdownMenuItem>
568
+ </DropdownMenuContent>
569
+ </DropdownMenu>
570
+ ```
571
+
572
+ **Estilos disabled**: `opacity-50`, `pointer-events-none`, `data-[disabled=true]`
573
+
574
+ ### Controlado
575
+
576
+ ```tsx
577
+ import { useState } from "react";
578
+
579
+ function App() {
580
+ const [open, setOpen] = useState(false);
581
+
582
+ return (
583
+ <DropdownMenu open={open} onOpenChange={setOpen}>
584
+ <DropdownMenuTrigger asChild>
585
+ <Button>Menu</Button>
586
+ </DropdownMenuTrigger>
587
+ <DropdownMenuContent>
588
+ <DropdownMenuItem onClick={() => setOpen(false)}>
589
+ Action (closes menu)
590
+ </DropdownMenuItem>
591
+ </DropdownMenuContent>
592
+ </DropdownMenu>
593
+ );
594
+ }
595
+ ```
596
+
597
+ ## Casos de Uso Comunes
598
+
599
+ **User menus**: Avatar menu con profile, settings, logout
600
+ **Action menus**: File actions, row actions, more options
601
+ **Navigation**: Nested navigation menus
602
+ **Filters/Options**: View settings, panel positions
603
+ **Context actions**: Edit, share, delete operations
604
+ **Bulk actions**: Select multiple items, apply actions
605
+
606
+ ## Estilos Base
607
+
608
+ ### Items (MenuItem, SubTrigger)
609
+
610
+ - **Height**: `min-h-10` (40px minimum, can grow with content)
611
+ - **Padding**: `px-3` (horizontal), no vertical padding (height defines size)
612
+ - **Rounded corners**: `first:rounded-t-sm`, `last:rounded-b-sm` (only first/last items)
613
+ - **Icon constraints**: None - icons use their natural sizes
614
+ - **Gap**: `gap-2` between content elements
615
+
616
+ ### CheckboxItem y RadioItem
617
+
618
+ - **Height**: `min-h-10` (40px minimum)
619
+ - **Padding**: `pl-8` (left), `pr-10` (right) - space for indicators
620
+ - **Rounded corners**: `first:rounded-t-sm`, `last:rounded-b-sm`
621
+
622
+ ### Label
623
+
624
+ - **Height**: `min-h-10` (40px minimum)
625
+ - **Padding**: `px-3` horizontal
626
+ - **Display**: `flex items-center` for vertical centering
627
+
628
+ ### Content
629
+
630
+ - **Padding**: No padding on container (items handle their own spacing)
631
+ - **Border radius**: `rounded-md`
632
+ - **Min width**: `min-w-[8rem]`
633
+
634
+ ### Separator
635
+
636
+ - **Height**: `h-px`
637
+ - **Margins**: No margins (removed `-mx-1 my-1`)
638
+ - **Background**: `bg-border`
639
+
640
+ ## Estados y Data Attributes
641
+
642
+ ### DropdownMenuItem States
643
+
644
+ - **Focus**: `data-[highlighted]` → `bg-accent`, `text-accent-foreground`
645
+ - **Disabled**: `data-[disabled=true]` → `opacity-50`, `pointer-events-none`
646
+ - **Destructive**: `data-[variant=destructive]` → `text-destructive`
647
+
648
+ ### DropdownMenuSubTrigger States
649
+
650
+ - **Open**: `data-[state=open]` `bg-accent`, `text-accent-foreground`
651
+
652
+ ### Content Animations
653
+
654
+ - **Opening**: `data-[state=open]` `animate-in`, `fade-in-0`, `zoom-in-95`
655
+ - **Closing**: `data-[state=closed]` `animate-out`, `fade-out-0`, `zoom-out-95`
656
+ - **Slide in**: `data-[side=top|right|bottom|left]` direccional
657
+
658
+ ## Navegación por Teclado
659
+
660
+ - ✅ **Arrow Up/Down**: Navega entre items
661
+ - ✅ **Arrow Right**: Abre submenú (si disponible)
662
+ - ✅ **Arrow Left**: Cierra submenú
663
+ - **Enter/Space**: Activa item enfocado
664
+ - ✅ **Escape**: Cierra menú
665
+ - ✅ **Tab**: Mueve focus fuera del menú
666
+ - ✅ **Home/End**: Primer/último item (si `loop={true}`)
667
+ - **Type ahead**: Busca item por primera letra
668
+
669
+ ## Accesibilidad
670
+
671
+ - ✅ **ARIA**: `role="menu"` en content, `role="menuitem"` en items
672
+ - ✅ **Keyboard navigation**: Navegación completa por teclado
673
+ - **Focus management**: Focus visible y restaurado correctamente
674
+ - ✅ **Screen readers**: Anuncia items, estado de checkboxes/radios, disabled
675
+ - ✅ **Disabled items**: `aria-disabled="true"` en items deshabilitados
676
+ - ✅ **Submenus**: `aria-haspopup="menu"` en SubTriggers
677
+ - ✅ **Checked state**: `aria-checked` en CheckboxItem y RadioItem
678
+
679
+ ## Notas de Implementación
680
+
681
+ - **Basado en Radix UI**: `@radix-ui/react-dropdown-menu`
682
+ - **Click to open**: Se activa con click (diferente de ContextMenu que usa right-click)
683
+ - **Auto positioning**: Portal con posicionamiento automático
684
+ - **Collision detection**: Evita salirse del viewport
685
+ - **Auto-close**: Cierra al seleccionar item (excepto checkboxes/radios)
686
+ - **Focus trap**: Mientras está abierto, focus queda dentro del menú
687
+ - **Portal rendering**: Content se renderiza en `document.body` por defecto
688
+ - **SVG auto-sizing**: Iconos automáticamente `size-4` y color muted
689
+ - **Inset support**: `inset` prop agrega `pl-8` para alinear con items con iconos
690
+ - **ChevronRightIcon**: Incluido automáticamente en SubTriggers
691
+ - **CheckIcon/CircleIcon**: Incluidos automáticamente en CheckboxItem/RadioItem
692
+ - **Variant destructive**: SVGs ignoran color muted y usan `text-destructive`
693
+ - **Default sideOffset**: 4px de distancia desde trigger
694
+
695
+ ## Posicionamiento
696
+
697
+ Control completo del posicionamiento del menú:
698
+
699
+ ```tsx
700
+ <DropdownMenuContent
701
+ align="end" // "start" | "center" | "end"
702
+ side="bottom" // "top" | "right" | "bottom" | "left"
703
+ sideOffset={8} // distancia desde trigger
704
+ alignOffset={-4} // offset horizontal
705
+ className="w-64" // ancho custom
706
+ >
707
+ {/* ... */}
708
+ </DropdownMenuContent>
709
+ ```
710
+
711
+ **Ejemplos comunes**:
712
+
713
+ - User menu (top-right avatar): `align="end"`
714
+ - Actions menu (more button): `align="end"`
715
+ - Navigation menu: `align="start"`
716
+
717
+ ## Troubleshooting
718
+
719
+ **Menú no aparece**: Verifica que DropdownMenuTrigger tenga `asChild` si usas Button custom
720
+ **Items no responden a click**: Usa `onSelect` en lugar de `onClick`
721
+ **Iconos mal alineados**: Usa `inset` en items sin iconos cuando otros sí tienen
722
+ **Menú no cierra al seleccionar**: CheckboxItems y RadioItems no cierran automáticamente (comportamiento esperado)
723
+ **Submenú no abre**: Verifica que estés usando `DropdownMenuSub` + `DropdownMenuSubTrigger` + `DropdownMenuSubContent`
724
+ **Shortcuts no ejecutan**: `DropdownMenuShortcut` es solo visual, implementa lógica en `onSelect`
725
+ **Focus se pierde al cerrar**: Usa `onCloseAutoFocus` para control custom del focus
726
+ **Posición incorrecta**: Verifica `align` y `side` props, ajusta con `sideOffset` y `alignOffset`
727
+ **Trigger no muestra estado**: Radix no cambia apariencia del trigger automáticamente, usa estado controlado
728
+
729
+ ## Diferencias con ContextMenu
730
+
731
+ | Aspecto | DropdownMenu | ContextMenu |
732
+ | -------------------- | --------------------------- | --------------------- |
733
+ | **Activación** | Click izquierdo | Click derecho |
734
+ | **Trigger** | Botón/elemento visible | Área target |
735
+ | **Uso común** | Menús de acción, navegación | Acciones contextuales |
736
+ | **Indicador visual** | Botón con chevron | Sin indicador |
737
+ | **Posicionamiento** | Relativo al trigger | Posición del cursor |
738
+
739
+ ## Referencias
740
+
741
+ - **Radix UI Dropdown Menu**: https://www.radix-ui.com/primitives/docs/components/dropdown-menu
742
+ - **shadcn/ui Dropdown Menu**: https://ui.shadcn.com/docs/components/dropdown-menu
743
+ - **ARIA Menu Pattern**: https://www.w3.org/WAI/ARIA/apg/patterns/menu-button/