@adamosuiteservices/ui 2.11.14 → 2.11.16

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 (41) hide show
  1. package/dist/colors.css +1 -1
  2. package/dist/styles.css +1 -1
  3. package/dist/themes.css +1 -1
  4. package/docs/AI-GUIDE.md +321 -321
  5. package/docs/components/layout/sidebar.md +399 -399
  6. package/docs/components/layout/toaster.md +436 -436
  7. package/docs/components/ui/accordion-rounded.md +584 -584
  8. package/docs/components/ui/accordion.md +269 -269
  9. package/docs/components/ui/button-group.md +984 -984
  10. package/docs/components/ui/button.md +1137 -1137
  11. package/docs/components/ui/calendar.md +1159 -1159
  12. package/docs/components/ui/card.md +1455 -1455
  13. package/docs/components/ui/checkbox.md +292 -292
  14. package/docs/components/ui/collapsible.md +323 -323
  15. package/docs/components/ui/command.md +454 -454
  16. package/docs/components/ui/context-menu.md +540 -540
  17. package/docs/components/ui/dialog.md +628 -628
  18. package/docs/components/ui/dropdown-menu.md +709 -709
  19. package/docs/components/ui/field.md +706 -706
  20. package/docs/components/ui/hover-card.md +446 -446
  21. package/docs/components/ui/input.md +362 -362
  22. package/docs/components/ui/kbd.md +434 -434
  23. package/docs/components/ui/label.md +359 -359
  24. package/docs/components/ui/pagination.md +650 -650
  25. package/docs/components/ui/popover.md +536 -536
  26. package/docs/components/ui/progress.md +182 -182
  27. package/docs/components/ui/radio-group.md +311 -311
  28. package/docs/components/ui/select.md +352 -352
  29. package/docs/components/ui/separator.md +214 -214
  30. package/docs/components/ui/sheet.md +142 -142
  31. package/docs/components/ui/skeleton.md +140 -140
  32. package/docs/components/ui/slider.md +341 -341
  33. package/docs/components/ui/spinner.md +170 -170
  34. package/docs/components/ui/switch.md +408 -408
  35. package/docs/components/ui/tabs-underline.md +106 -106
  36. package/docs/components/ui/tabs.md +122 -122
  37. package/docs/components/ui/textarea.md +243 -243
  38. package/docs/components/ui/toggle.md +237 -237
  39. package/docs/components/ui/tooltip.md +317 -317
  40. package/docs/components/ui/typography.md +280 -280
  41. package/package.json +1 -1
@@ -1,540 +1,540 @@
1
- # Context Menu
2
-
3
- Menú contextual al hacer clic derecho, basado en Radix UI. Soporta submenús, checkboxes, radio groups, separadores, y shortcuts visuales. Útil para acciones contextuales en elementos.
4
-
5
- ## Importación
6
-
7
- ```tsx
8
- import {
9
- ContextMenu,
10
- ContextMenuTrigger,
11
- ContextMenuContent,
12
- ContextMenuItem,
13
- ContextMenuCheckboxItem,
14
- ContextMenuRadioGroup,
15
- ContextMenuRadioItem,
16
- ContextMenuLabel,
17
- ContextMenuSeparator,
18
- ContextMenuShortcut,
19
- ContextMenuSub,
20
- ContextMenuSubContent,
21
- ContextMenuSubTrigger,
22
- ContextMenuGroup,
23
- ContextMenuPortal,
24
- } from "@adamosuiteservices/ui/context-menu";
25
- ```
26
-
27
- ## Anatomía
28
-
29
- ```tsx
30
- <ContextMenu>
31
- <ContextMenuTrigger>Right click here</ContextMenuTrigger>
32
- <ContextMenuContent>
33
- <ContextMenuLabel>Actions</ContextMenuLabel>
34
- <ContextMenuItem>
35
- Edit
36
- <ContextMenuShortcut>⌘E</ContextMenuShortcut>
37
- </ContextMenuItem>
38
- <ContextMenuSeparator />
39
- <ContextMenuCheckboxItem checked={showBookmarks}>
40
- Show Bookmarks
41
- </ContextMenuCheckboxItem>
42
- <ContextMenuSeparator />
43
- <ContextMenuSub>
44
- <ContextMenuSubTrigger>More Options</ContextMenuSubTrigger>
45
- <ContextMenuSubContent>
46
- <ContextMenuItem>Export</ContextMenuItem>
47
- </ContextMenuSubContent>
48
- </ContextMenuSub>
49
- </ContextMenuContent>
50
- </ContextMenu>
51
- ```
52
-
53
- **Componentes**: 14 (Trigger, Content, Item, CheckboxItem, RadioGroup, RadioItem, Label, Separator, Shortcut, Sub, SubTrigger, SubContent, Group, Portal)
54
-
55
- ## Props Principales
56
-
57
- ### ContextMenu (Root)
58
-
59
- | Prop | Tipo | Default | Descripción |
60
- | -------------- | ------------------------- | ------- | -------------------------------------------------- |
61
- | `onOpenChange` | `(open: boolean) => void` | - | Callback cuando cambia estado del menú |
62
- | `modal` | `boolean` | `true` | Comportamiento modal (bloquea interacción externa) |
63
-
64
- ### ContextMenuTrigger
65
-
66
- | Prop | Tipo | Descripción |
67
- | ----------- | --------- | -------------------------------------------------- |
68
- | `asChild` | `boolean` | Pasa props al child en lugar de renderizar wrapper |
69
- | `disabled` | `boolean` | Desactiva el trigger |
70
- | `className` | `string` | Clases CSS adicionales |
71
-
72
- ### ContextMenuContent
73
-
74
- | Prop | Tipo | Default | Descripción |
75
- | ---------------------- | ----------------- | ------- | --------------------------------------------- |
76
- | `alignOffset` | `number` | `0` | Offset del alineamiento |
77
- | `sideOffset` | `number` | `0` | Distancia desde trigger |
78
- | `className` | `string` | - | Clases CSS adicionales |
79
- | `loop` | `boolean` | `false` | Navegación circular con arrow keys |
80
- | `onCloseAutoFocus` | `(event) => void` | - | Callback al cerrar (antes de restaurar focus) |
81
- | `onEscapeKeyDown` | `(event) => void` | - | Callback al presionar Escape |
82
- | `onPointerDownOutside` | `(event) => void` | - | Callback al hacer clic fuera |
83
-
84
- **Estilos default**: `min-w-[8rem]`, `max-h-(--radix-context-menu-content-available-height)`, scroll automático, `z-50`
85
-
86
- ### ContextMenuItem
87
-
88
- | Prop | Tipo | Default | Descripción |
89
- | ----------- | ---------------------------- | ----------- | --------------------------------------- |
90
- | `onSelect` | `(event) => void` | - | Callback al seleccionar (click o Enter) |
91
- | `disabled` | `boolean` | `false` | Desactiva el item |
92
- | `inset` | `boolean` | `false` | Agrega padding izquierdo (`pl-8`) |
93
- | `variant` | `"default" \| "destructive"` | `"default"` | Estilo del item |
94
- | `className` | `string` | - | Clases CSS adicionales |
95
-
96
- **Variantes**:
97
-
98
- - `default`: `focus:bg-accent`, `focus:text-accent-foreground`
99
- - `destructive`: `text-destructive`, `focus:bg-destructive/10`, `focus:text-destructive`
100
-
101
- ### ContextMenuCheckboxItem
102
-
103
- | Prop | Tipo | Descripción |
104
- | ----------------- | ---------------------------- | -------------------------- |
105
- | `checked` | `boolean \| "indeterminate"` | Estado del checkbox |
106
- | `onCheckedChange` | `(checked: boolean) => void` | Callback al cambiar estado |
107
- | `disabled` | `boolean` | Desactiva el item |
108
- | `className` | `string` | Clases CSS adicionales |
109
-
110
- **Indicador**: Check icon (Material Symbol "check") automático cuando `checked={true}`, posicionado con `pl-8`
111
-
112
- ### ContextMenuRadioGroup
113
-
114
- | Prop | Tipo | Descripción |
115
- | --------------- | ------------------------- | ------------------------- |
116
- | `value` | `string` | Valor seleccionado |
117
- | `onValueChange` | `(value: string) => void` | Callback al cambiar valor |
118
-
119
- ### ContextMenuRadioItem
120
-
121
- | Prop | Tipo | Descripción |
122
- | ----------- | --------- | ---------------------- |
123
- | `value` | `string` | Valor único del item |
124
- | `disabled` | `boolean` | Desactiva el item |
125
- | `className` | `string` | Clases CSS adicionales |
126
-
127
- **Indicador**: Circle icon (Material Symbol "circle") automático cuando seleccionado, posicionado con `pl-8`
128
-
129
- ### ContextMenuSub
130
-
131
- | Prop | Tipo | Descripción |
132
- | -------------- | ------------------------- | ----------------------------- |
133
- | `open` | `boolean` | Estado controlado del submenú |
134
- | `onOpenChange` | `(open: boolean) => void` | Callback al cambiar estado |
135
-
136
- ### ContextMenuSubTrigger
137
-
138
- | Prop | Tipo | Descripción |
139
- | ----------- | --------- | -------------------------- |
140
- | `disabled` | `boolean` | Desactiva el trigger |
141
- | `inset` | `boolean` | Padding izquierdo (`pl-8`) |
142
- | `className` | `string` | Clases CSS adicionales |
143
-
144
- **Indicador**: Chevron right icon (Material Symbol "chevron_right") automático con `ml-auto`
145
-
146
- ### ContextMenuLabel
147
-
148
- | Prop | Tipo | Descripción |
149
- | ----------- | --------- | -------------------------- |
150
- | `inset` | `boolean` | Padding izquierdo (`pl-8`) |
151
- | `className` | `string` | Clases CSS adicionales |
152
-
153
- **Estilos**: `text-foreground`, `font-medium`
154
-
155
- ### ContextMenuShortcut
156
-
157
- | Prop | Tipo | Descripción |
158
- | ----------- | -------- | ---------------------- |
159
- | `className` | `string` | Clases CSS adicionales |
160
-
161
- **Estilos**: `text-muted-foreground`, `text-xs`, `tracking-widest`, `ml-auto`
162
-
163
- ## Patrones de Uso
164
-
165
- ### Básico
166
-
167
- ```tsx
168
- <ContextMenu>
169
- <ContextMenuTrigger className="flex h-24 w-48 items-center justify-center rounded-md border">
170
- Right click here
171
- </ContextMenuTrigger>
172
- <ContextMenuContent>
173
- <ContextMenuItem>Profile</ContextMenuItem>
174
- <ContextMenuItem>Billing</ContextMenuItem>
175
- <ContextMenuItem>Team</ContextMenuItem>
176
- <ContextMenuItem>Settings</ContextMenuItem>
177
- </ContextMenuContent>
178
- </ContextMenu>
179
- ```
180
-
181
- ### Con Iconos y Shortcuts
182
-
183
- ```tsx
184
- import { Icon } from "@adamosuiteservices/ui/icon";
185
-
186
- <ContextMenu>
187
- <ContextMenuTrigger>Right click</ContextMenuTrigger>
188
- <ContextMenuContent>
189
- <ContextMenuItem>
190
- <Icon symbol="person" />
191
- Profile
192
- <ContextMenuShortcut>⌘P</ContextMenuShortcut>
193
- </ContextMenuItem>
194
- <ContextMenuItem>
195
- <Icon symbol="settings" />
196
- Settings
197
- <ContextMenuShortcut>⌘S</ContextMenuShortcut>
198
- </ContextMenuItem>
199
- <ContextMenuSeparator />
200
- <ContextMenuItem variant="destructive">
201
- <Icon symbol="logout" />
202
- Log Out
203
- <ContextMenuShortcut>⌘Q</ContextMenuShortcut>
204
- </ContextMenuItem>
205
- </ContextMenuContent>
206
- </ContextMenu>;
207
- ```
208
-
209
- **Nota**: Material Symbol icons automáticamente `text-lg` (18px), `text-muted-foreground` (excepto en variant destructive). SVG icons soportados con `size-4`.
210
-
211
- ### Con Checkboxes
212
-
213
- ```tsx
214
- import { useState } from "react";
215
-
216
- function Preferences() {
217
- const [showBookmarks, setShowBookmarks] = useState(true);
218
- const [showFullUrls, setShowFullUrls] = useState(false);
219
-
220
- return (
221
- <ContextMenu>
222
- <ContextMenuTrigger>Right click</ContextMenuTrigger>
223
- <ContextMenuContent>
224
- <ContextMenuCheckboxItem
225
- checked={showBookmarks}
226
- onCheckedChange={setShowBookmarks}
227
- >
228
- Show Bookmarks
229
- </ContextMenuCheckboxItem>
230
- <ContextMenuCheckboxItem
231
- checked={showFullUrls}
232
- onCheckedChange={setShowFullUrls}
233
- >
234
- Show Full URLs
235
- </ContextMenuCheckboxItem>
236
- </ContextMenuContent>
237
- </ContextMenu>
238
- );
239
- }
240
- ```
241
-
242
- ### Con Radio Group
243
-
244
- ```tsx
245
- import { useState } from "react";
246
-
247
- function Settings() {
248
- const [theme, setTheme] = useState("light");
249
-
250
- return (
251
- <ContextMenu>
252
- <ContextMenuTrigger>Right click</ContextMenuTrigger>
253
- <ContextMenuContent>
254
- <ContextMenuLabel>Theme</ContextMenuLabel>
255
- <ContextMenuRadioGroup value={theme} onValueChange={setTheme}>
256
- <ContextMenuRadioItem value="light">Light</ContextMenuRadioItem>
257
- <ContextMenuRadioItem value="dark">Dark</ContextMenuRadioItem>
258
- <ContextMenuRadioItem value="system">System</ContextMenuRadioItem>
259
- </ContextMenuRadioGroup>
260
- </ContextMenuContent>
261
- </ContextMenu>
262
- );
263
- }
264
- ```
265
-
266
- ### Con Submenú
267
-
268
- ```tsx
269
- import { Icon } from "@adamosuiteservices/ui/icon";
270
-
271
- <ContextMenu>
272
- <ContextMenuTrigger>Right click</ContextMenuTrigger>
273
- <ContextMenuContent>
274
- <ContextMenuItem>Edit</ContextMenuItem>
275
- <ContextMenuItem>Duplicate</ContextMenuItem>
276
- <ContextMenuSeparator />
277
- <ContextMenuSub>
278
- <ContextMenuSubTrigger>
279
- <Icon symbol="more_horiz" />
280
- More Options
281
- </ContextMenuSubTrigger>
282
- <ContextMenuSubContent>
283
- <ContextMenuItem>
284
- <Icon symbol="download" />
285
- Export
286
- </ContextMenuItem>
287
- <ContextMenuItem>
288
- <Icon symbol="share" />
289
- Share
290
- </ContextMenuItem>
291
- </ContextMenuSubContent>
292
- </ContextMenuSub>
293
- </ContextMenuContent>
294
- </ContextMenu>;
295
- ```
296
-
297
- ### File Manager (Multiple Items)
298
-
299
- ```tsx
300
- import { Icon } from "@adamosuiteservices/ui/icon";
301
-
302
- <div className="grid grid-cols-3 gap-4">
303
- <ContextMenu>
304
- <ContextMenuTrigger className="flex h-24 flex-col items-center justify-center rounded-lg border p-4">
305
- <Icon symbol="description" className="text-2xl mb-2" />
306
- <span className="text-sm">Document.pdf</span>
307
- </ContextMenuTrigger>
308
- <ContextMenuContent>
309
- <ContextMenuItem>Open</ContextMenuItem>
310
- <ContextMenuItem>Open with...</ContextMenuItem>
311
- <ContextMenuSeparator />
312
- <ContextMenuItem>
313
- <Icon symbol="content_copy" />
314
- Copy
315
- </ContextMenuItem>
316
- <ContextMenuItem variant="destructive">
317
- <Icon symbol="delete" />
318
- Delete
319
- </ContextMenuItem>
320
- </ContextMenuContent>
321
- </ContextMenu>
322
-
323
- <ContextMenu>
324
- <ContextMenuTrigger className="flex h-24 flex-col items-center justify-center rounded-lg border p-4">
325
- <Icon symbol="folder" className="text-2xl mb-2" />
326
- <span className="text-sm">Projects</span>
327
- </ContextMenuTrigger>
328
- <ContextMenuContent>
329
- <ContextMenuItem>Open</ContextMenuItem>
330
- <ContextMenuItem>Open in new window</ContextMenuItem>
331
- <ContextMenuSeparator />
332
- <ContextMenuItem>
333
- <Icon symbol="content_copy" />
334
- Copy
335
- </ContextMenuItem>
336
- <ContextMenuItem variant="destructive">
337
- <TrashIcon />
338
- Delete
339
- </ContextMenuItem>
340
- </ContextMenuContent>
341
- </ContextMenu>
342
- </div>;
343
- ```
344
-
345
- ### Con Labels y Grupos
346
-
347
- ```tsx
348
- import { Icon } from "@adamosuiteservices/ui/icon";
349
-
350
- <ContextMenu>
351
- <ContextMenuTrigger>Right click</ContextMenuTrigger>
352
- <ContextMenuContent className="w-56">
353
- <ContextMenuLabel>File Operations</ContextMenuLabel>
354
- <ContextMenuItem>
355
- <Icon symbol="description" />
356
- New File
357
- <ContextMenuShortcut>⌘N</ContextMenuShortcut>
358
- </ContextMenuItem>
359
- <ContextMenuItem>
360
- <Icon symbol="folder" />
361
- New Folder
362
- <ContextMenuShortcut>⌘⇧N</ContextMenuShortcut>
363
- </ContextMenuItem>
364
-
365
- <ContextMenuSeparator />
366
-
367
- <ContextMenuLabel>Edit</ContextMenuLabel>
368
- <ContextMenuItem>
369
- <Icon symbol="content_copy" />
370
- Copy
371
- <ContextMenuShortcut>⌘C</ContextMenuShortcut>
372
- </ContextMenuItem>
373
- <ContextMenuItem>
374
- <Icon symbol="edit" />
375
- Paste
376
- <ContextMenuShortcut>⌘V</ContextMenuShortcut>
377
- </ContextMenuItem>
378
- </ContextMenuContent>
379
- </ContextMenu>;
380
- ```
381
-
382
- ### Items Deshabilitados
383
-
384
- ```tsx
385
- <ContextMenu>
386
- <ContextMenuTrigger>Right click</ContextMenuTrigger>
387
- <ContextMenuContent>
388
- <ContextMenuItem>Available Action</ContextMenuItem>
389
- <ContextMenuItem disabled>Disabled Action</ContextMenuItem>
390
- <ContextMenuItem>Another Action</ContextMenuItem>
391
- <ContextMenuSeparator />
392
- <ContextMenuItem variant="destructive" disabled>
393
- Cannot Delete
394
- </ContextMenuItem>
395
- </ContextMenuContent>
396
- </ContextMenu>
397
- ```
398
-
399
- **Estilos disabled**: `opacity-50`, `pointer-events-none`, `data-[disabled=true]`
400
-
401
- ### Con Callbacks
402
-
403
- ```tsx
404
- function App() {
405
- const handleAction = (action: string) => {
406
- console.log(`Action: ${action}`);
407
- };
408
-
409
- return (
410
- <ContextMenu>
411
- <ContextMenuTrigger>Right click</ContextMenuTrigger>
412
- <ContextMenuContent>
413
- <ContextMenuItem onSelect={() => handleAction("edit")}>
414
- Edit
415
- </ContextMenuItem>
416
- <ContextMenuItem onSelect={() => handleAction("copy")}>
417
- Copy
418
- </ContextMenuItem>
419
- <ContextMenuItem
420
- variant="destructive"
421
- onSelect={() => handleAction("delete")}
422
- >
423
- Delete
424
- </ContextMenuItem>
425
- </ContextMenuContent>
426
- </ContextMenu>
427
- );
428
- }
429
- ```
430
-
431
- ### Con asChild Pattern
432
-
433
- ```tsx
434
- // Usar elemento custom como trigger sin wrapper adicional
435
- <ContextMenu>
436
- <ContextMenuTrigger asChild>
437
- <button className="custom-button">Custom Trigger</button>
438
- </ContextMenuTrigger>
439
- <ContextMenuContent>
440
- <ContextMenuItem>Action 1</ContextMenuItem>
441
- <ContextMenuItem>Action 2</ContextMenuItem>
442
- </ContextMenuContent>
443
- </ContextMenu>
444
- ```
445
-
446
- ## Casos de Uso Comunes
447
-
448
- **File managers**: Acciones contextuales en archivos/carpetas
449
- **Image galleries**: Opciones sobre imágenes (download, share, delete)
450
- **Text editors**: Copy, paste, format options
451
- **Browser menus**: Back, forward, reload, more tools
452
- **Data tables**: Row actions (edit, duplicate, delete)
453
- **Canvas/diagram apps**: Acciones sobre elementos seleccionados
454
-
455
- ## Estados y Data Attributes
456
-
457
- ### ContextMenuItem States
458
-
459
- - **Focus**: `data-[highlighted]` → `bg-accent`, `text-accent-foreground`
460
- - **Disabled**: `data-[disabled=true]` → `opacity-50`, `pointer-events-none`
461
- - **Destructive**: `data-[variant=destructive]` → `text-destructive`
462
-
463
- ### ContextMenuSubTrigger States
464
-
465
- - **Open**: `data-[state=open]` → `bg-accent`, `text-accent-foreground`
466
-
467
- ### Content Animations
468
-
469
- - **Opening**: `data-[state=open]` → `animate-in`, `fade-in-0`, `zoom-in-95`
470
- - **Closing**: `data-[state=closed]` → `animate-out`, `fade-out-0`, `zoom-out-95`
471
- - **Slide in**: `data-[side=top|right|bottom|left]` → direccional
472
-
473
- ## Navegación por Teclado
474
-
475
- - ✅ **Arrow Up/Down**: Navega entre items
476
- - ✅ **Arrow Right**: Abre submenú (si disponible)
477
- - ✅ **Arrow Left**: Cierra submenú
478
- - ✅ **Enter/Space**: Activa item enfocado
479
- - ✅ **Escape**: Cierra menú
480
- - ✅ **Tab**: Mueve focus fuera del menú
481
- - ✅ **Home/End**: Primer/último item (si `loop={true}`)
482
- - ✅ **Type ahead**: Busca item por primera letra
483
-
484
- ## Accesibilidad
485
-
486
- - ✅ **ARIA**: `role="menu"` en content, `role="menuitem"` en items
487
- - ✅ **Keyboard navigation**: Navegación completa por teclado
488
- - ✅ **Focus management**: Focus visible y restaurado correctamente
489
- - ✅ **Screen readers**: Anuncia items, estado de checkboxes/radios, disabled
490
- - ✅ **Disabled items**: `aria-disabled="true"` en items deshabilitados
491
- - ✅ **Submenus**: `aria-haspopup="menu"` en SubTriggers
492
- - ✅ **Checked state**: `aria-checked` en CheckboxItem y RadioItem
493
-
494
- ## Notas de Implementación
495
-
496
- - **Basado en Radix UI**: `@radix-ui/react-context-menu`
497
- - **Right click only**: Se activa solo con botón derecho del mouse
498
- - **Auto positioning**: Portal con posicionamiento automático
499
- - **Collision detection**: Evita salirse del viewport
500
- - **Auto-close**: Cierra al seleccionar item (excepto checkboxes/radios)
501
- - **Focus trap**: Mientras está abierto, focus queda dentro del menú
502
- - **Portal rendering**: Content se renderiza en `document.body` por defecto
503
- - **SVG auto-sizing**: Iconos automáticamente `size-4` y color muted
504
- - **Inset support**: `inset` prop agrega `pl-8` para alinear con items con iconos
505
- - **ChevronRightIcon**: Incluido automáticamente en SubTriggers
506
- - **CheckIcon/CircleIcon**: Incluidos automáticamente en CheckboxItem/RadioItem
507
- - **Variant destructive**: SVGs ignoran color muted y usan `text-destructive`
508
-
509
- ## Posicionamiento
510
-
511
- Por defecto, el menú aparece en la posición del cursor. Se puede personalizar:
512
-
513
- ```tsx
514
- <ContextMenuContent alignOffset={10} sideOffset={5} className="w-64">
515
- {/* ... */}
516
- </ContextMenuContent>
517
- ```
518
-
519
- **Opciones**:
520
-
521
- - `alignOffset`: Ajusta alineamiento horizontal
522
- - `sideOffset`: Distancia desde el cursor
523
- - `className="w-*"`: Controla ancho del menú
524
-
525
- ## Troubleshooting
526
-
527
- **Menú no aparece al hacer clic derecho**: Verifica que estés usando `ContextMenuTrigger` correctamente y que no haya CSS que bloquee eventos
528
- **Items no responden a click**: Asegúrate de usar `onSelect` en lugar de `onClick`
529
- **Iconos mal alineados**: Usa `inset` en items sin iconos cuando otros sí tienen
530
- **Menú no cierra al seleccionar**: CheckboxItems y RadioItems no cierran automáticamente (comportamiento esperado)
531
- **Submenú no abre**: Verifica que estés usando `ContextMenuSub` + `ContextMenuSubTrigger` + `ContextMenuSubContent`
532
- **Shortcuts no ejecutan**: `ContextMenuShortcut` es solo visual, implementa lógica en `onSelect`
533
- **Focus se pierde al cerrar**: Usa `onCloseAutoFocus` para control custom del focus
534
- **Posición incorrecta**: Verifica que el trigger no tenga `transform` CSS que afecte portales
535
-
536
- ## Referencias
537
-
538
- - **Radix UI Context Menu**: https://www.radix-ui.com/primitives/docs/components/context-menu
539
- - **shadcn/ui Context Menu**: https://ui.shadcn.com/docs/components/context-menu
540
- - **ARIA Menu Pattern**: https://www.w3.org/WAI/ARIA/apg/patterns/menu/
1
+ # Context Menu
2
+
3
+ Menú contextual al hacer clic derecho, basado en Radix UI. Soporta submenús, checkboxes, radio groups, separadores, y shortcuts visuales. Útil para acciones contextuales en elementos.
4
+
5
+ ## Importación
6
+
7
+ ```tsx
8
+ import {
9
+ ContextMenu,
10
+ ContextMenuTrigger,
11
+ ContextMenuContent,
12
+ ContextMenuItem,
13
+ ContextMenuCheckboxItem,
14
+ ContextMenuRadioGroup,
15
+ ContextMenuRadioItem,
16
+ ContextMenuLabel,
17
+ ContextMenuSeparator,
18
+ ContextMenuShortcut,
19
+ ContextMenuSub,
20
+ ContextMenuSubContent,
21
+ ContextMenuSubTrigger,
22
+ ContextMenuGroup,
23
+ ContextMenuPortal,
24
+ } from "@adamosuiteservices/ui/context-menu";
25
+ ```
26
+
27
+ ## Anatomía
28
+
29
+ ```tsx
30
+ <ContextMenu>
31
+ <ContextMenuTrigger>Right click here</ContextMenuTrigger>
32
+ <ContextMenuContent>
33
+ <ContextMenuLabel>Actions</ContextMenuLabel>
34
+ <ContextMenuItem>
35
+ Edit
36
+ <ContextMenuShortcut>⌘E</ContextMenuShortcut>
37
+ </ContextMenuItem>
38
+ <ContextMenuSeparator />
39
+ <ContextMenuCheckboxItem checked={showBookmarks}>
40
+ Show Bookmarks
41
+ </ContextMenuCheckboxItem>
42
+ <ContextMenuSeparator />
43
+ <ContextMenuSub>
44
+ <ContextMenuSubTrigger>More Options</ContextMenuSubTrigger>
45
+ <ContextMenuSubContent>
46
+ <ContextMenuItem>Export</ContextMenuItem>
47
+ </ContextMenuSubContent>
48
+ </ContextMenuSub>
49
+ </ContextMenuContent>
50
+ </ContextMenu>
51
+ ```
52
+
53
+ **Componentes**: 14 (Trigger, Content, Item, CheckboxItem, RadioGroup, RadioItem, Label, Separator, Shortcut, Sub, SubTrigger, SubContent, Group, Portal)
54
+
55
+ ## Props Principales
56
+
57
+ ### ContextMenu (Root)
58
+
59
+ | Prop | Tipo | Default | Descripción |
60
+ | -------------- | ------------------------- | ------- | -------------------------------------------------- |
61
+ | `onOpenChange` | `(open: boolean) => void` | - | Callback cuando cambia estado del menú |
62
+ | `modal` | `boolean` | `true` | Comportamiento modal (bloquea interacción externa) |
63
+
64
+ ### ContextMenuTrigger
65
+
66
+ | Prop | Tipo | Descripción |
67
+ | ----------- | --------- | -------------------------------------------------- |
68
+ | `asChild` | `boolean` | Pasa props al child en lugar de renderizar wrapper |
69
+ | `disabled` | `boolean` | Desactiva el trigger |
70
+ | `className` | `string` | Clases CSS adicionales |
71
+
72
+ ### ContextMenuContent
73
+
74
+ | Prop | Tipo | Default | Descripción |
75
+ | ---------------------- | ----------------- | ------- | --------------------------------------------- |
76
+ | `alignOffset` | `number` | `0` | Offset del alineamiento |
77
+ | `sideOffset` | `number` | `0` | Distancia desde trigger |
78
+ | `className` | `string` | - | Clases CSS adicionales |
79
+ | `loop` | `boolean` | `false` | Navegación circular con arrow keys |
80
+ | `onCloseAutoFocus` | `(event) => void` | - | Callback al cerrar (antes de restaurar focus) |
81
+ | `onEscapeKeyDown` | `(event) => void` | - | Callback al presionar Escape |
82
+ | `onPointerDownOutside` | `(event) => void` | - | Callback al hacer clic fuera |
83
+
84
+ **Estilos default**: `min-w-[8rem]`, `max-h-(--radix-context-menu-content-available-height)`, scroll automático, `z-50`
85
+
86
+ ### ContextMenuItem
87
+
88
+ | Prop | Tipo | Default | Descripción |
89
+ | ----------- | ---------------------------- | ----------- | --------------------------------------- |
90
+ | `onSelect` | `(event) => void` | - | Callback al seleccionar (click o Enter) |
91
+ | `disabled` | `boolean` | `false` | Desactiva el item |
92
+ | `inset` | `boolean` | `false` | Agrega padding izquierdo (`pl-8`) |
93
+ | `variant` | `"default" \| "destructive"` | `"default"` | Estilo del item |
94
+ | `className` | `string` | - | Clases CSS adicionales |
95
+
96
+ **Variantes**:
97
+
98
+ - `default`: `focus:bg-accent`, `focus:text-accent-foreground`
99
+ - `destructive`: `text-destructive`, `focus:bg-destructive/10`, `focus:text-destructive`
100
+
101
+ ### ContextMenuCheckboxItem
102
+
103
+ | Prop | Tipo | Descripción |
104
+ | ----------------- | ---------------------------- | -------------------------- |
105
+ | `checked` | `boolean \| "indeterminate"` | Estado del checkbox |
106
+ | `onCheckedChange` | `(checked: boolean) => void` | Callback al cambiar estado |
107
+ | `disabled` | `boolean` | Desactiva el item |
108
+ | `className` | `string` | Clases CSS adicionales |
109
+
110
+ **Indicador**: Check icon (Material Symbol "check") automático cuando `checked={true}`, posicionado con `pl-8`
111
+
112
+ ### ContextMenuRadioGroup
113
+
114
+ | Prop | Tipo | Descripción |
115
+ | --------------- | ------------------------- | ------------------------- |
116
+ | `value` | `string` | Valor seleccionado |
117
+ | `onValueChange` | `(value: string) => void` | Callback al cambiar valor |
118
+
119
+ ### ContextMenuRadioItem
120
+
121
+ | Prop | Tipo | Descripción |
122
+ | ----------- | --------- | ---------------------- |
123
+ | `value` | `string` | Valor único del item |
124
+ | `disabled` | `boolean` | Desactiva el item |
125
+ | `className` | `string` | Clases CSS adicionales |
126
+
127
+ **Indicador**: Circle icon (Material Symbol "circle") automático cuando seleccionado, posicionado con `pl-8`
128
+
129
+ ### ContextMenuSub
130
+
131
+ | Prop | Tipo | Descripción |
132
+ | -------------- | ------------------------- | ----------------------------- |
133
+ | `open` | `boolean` | Estado controlado del submenú |
134
+ | `onOpenChange` | `(open: boolean) => void` | Callback al cambiar estado |
135
+
136
+ ### ContextMenuSubTrigger
137
+
138
+ | Prop | Tipo | Descripción |
139
+ | ----------- | --------- | -------------------------- |
140
+ | `disabled` | `boolean` | Desactiva el trigger |
141
+ | `inset` | `boolean` | Padding izquierdo (`pl-8`) |
142
+ | `className` | `string` | Clases CSS adicionales |
143
+
144
+ **Indicador**: Chevron right icon (Material Symbol "chevron_right") automático con `ml-auto`
145
+
146
+ ### ContextMenuLabel
147
+
148
+ | Prop | Tipo | Descripción |
149
+ | ----------- | --------- | -------------------------- |
150
+ | `inset` | `boolean` | Padding izquierdo (`pl-8`) |
151
+ | `className` | `string` | Clases CSS adicionales |
152
+
153
+ **Estilos**: `text-foreground`, `font-medium`
154
+
155
+ ### ContextMenuShortcut
156
+
157
+ | Prop | Tipo | Descripción |
158
+ | ----------- | -------- | ---------------------- |
159
+ | `className` | `string` | Clases CSS adicionales |
160
+
161
+ **Estilos**: `text-muted-foreground`, `text-xs`, `tracking-widest`, `ml-auto`
162
+
163
+ ## Patrones de Uso
164
+
165
+ ### Básico
166
+
167
+ ```tsx
168
+ <ContextMenu>
169
+ <ContextMenuTrigger className="flex h-24 w-48 items-center justify-center rounded-md border">
170
+ Right click here
171
+ </ContextMenuTrigger>
172
+ <ContextMenuContent>
173
+ <ContextMenuItem>Profile</ContextMenuItem>
174
+ <ContextMenuItem>Billing</ContextMenuItem>
175
+ <ContextMenuItem>Team</ContextMenuItem>
176
+ <ContextMenuItem>Settings</ContextMenuItem>
177
+ </ContextMenuContent>
178
+ </ContextMenu>
179
+ ```
180
+
181
+ ### Con Iconos y Shortcuts
182
+
183
+ ```tsx
184
+ import { Icon } from "@adamosuiteservices/ui/icon";
185
+
186
+ <ContextMenu>
187
+ <ContextMenuTrigger>Right click</ContextMenuTrigger>
188
+ <ContextMenuContent>
189
+ <ContextMenuItem>
190
+ <Icon symbol="person" />
191
+ Profile
192
+ <ContextMenuShortcut>⌘P</ContextMenuShortcut>
193
+ </ContextMenuItem>
194
+ <ContextMenuItem>
195
+ <Icon symbol="settings" />
196
+ Settings
197
+ <ContextMenuShortcut>⌘S</ContextMenuShortcut>
198
+ </ContextMenuItem>
199
+ <ContextMenuSeparator />
200
+ <ContextMenuItem variant="destructive">
201
+ <Icon symbol="logout" />
202
+ Log Out
203
+ <ContextMenuShortcut>⌘Q</ContextMenuShortcut>
204
+ </ContextMenuItem>
205
+ </ContextMenuContent>
206
+ </ContextMenu>;
207
+ ```
208
+
209
+ **Nota**: Material Symbol icons automáticamente `text-lg` (18px), `text-muted-foreground` (excepto en variant destructive). SVG icons soportados con `size-4`.
210
+
211
+ ### Con Checkboxes
212
+
213
+ ```tsx
214
+ import { useState } from "react";
215
+
216
+ function Preferences() {
217
+ const [showBookmarks, setShowBookmarks] = useState(true);
218
+ const [showFullUrls, setShowFullUrls] = useState(false);
219
+
220
+ return (
221
+ <ContextMenu>
222
+ <ContextMenuTrigger>Right click</ContextMenuTrigger>
223
+ <ContextMenuContent>
224
+ <ContextMenuCheckboxItem
225
+ checked={showBookmarks}
226
+ onCheckedChange={setShowBookmarks}
227
+ >
228
+ Show Bookmarks
229
+ </ContextMenuCheckboxItem>
230
+ <ContextMenuCheckboxItem
231
+ checked={showFullUrls}
232
+ onCheckedChange={setShowFullUrls}
233
+ >
234
+ Show Full URLs
235
+ </ContextMenuCheckboxItem>
236
+ </ContextMenuContent>
237
+ </ContextMenu>
238
+ );
239
+ }
240
+ ```
241
+
242
+ ### Con Radio Group
243
+
244
+ ```tsx
245
+ import { useState } from "react";
246
+
247
+ function Settings() {
248
+ const [theme, setTheme] = useState("light");
249
+
250
+ return (
251
+ <ContextMenu>
252
+ <ContextMenuTrigger>Right click</ContextMenuTrigger>
253
+ <ContextMenuContent>
254
+ <ContextMenuLabel>Theme</ContextMenuLabel>
255
+ <ContextMenuRadioGroup value={theme} onValueChange={setTheme}>
256
+ <ContextMenuRadioItem value="light">Light</ContextMenuRadioItem>
257
+ <ContextMenuRadioItem value="dark">Dark</ContextMenuRadioItem>
258
+ <ContextMenuRadioItem value="system">System</ContextMenuRadioItem>
259
+ </ContextMenuRadioGroup>
260
+ </ContextMenuContent>
261
+ </ContextMenu>
262
+ );
263
+ }
264
+ ```
265
+
266
+ ### Con Submenú
267
+
268
+ ```tsx
269
+ import { Icon } from "@adamosuiteservices/ui/icon";
270
+
271
+ <ContextMenu>
272
+ <ContextMenuTrigger>Right click</ContextMenuTrigger>
273
+ <ContextMenuContent>
274
+ <ContextMenuItem>Edit</ContextMenuItem>
275
+ <ContextMenuItem>Duplicate</ContextMenuItem>
276
+ <ContextMenuSeparator />
277
+ <ContextMenuSub>
278
+ <ContextMenuSubTrigger>
279
+ <Icon symbol="more_horiz" />
280
+ More Options
281
+ </ContextMenuSubTrigger>
282
+ <ContextMenuSubContent>
283
+ <ContextMenuItem>
284
+ <Icon symbol="download" />
285
+ Export
286
+ </ContextMenuItem>
287
+ <ContextMenuItem>
288
+ <Icon symbol="share" />
289
+ Share
290
+ </ContextMenuItem>
291
+ </ContextMenuSubContent>
292
+ </ContextMenuSub>
293
+ </ContextMenuContent>
294
+ </ContextMenu>;
295
+ ```
296
+
297
+ ### File Manager (Multiple Items)
298
+
299
+ ```tsx
300
+ import { Icon } from "@adamosuiteservices/ui/icon";
301
+
302
+ <div className="grid grid-cols-3 gap-4">
303
+ <ContextMenu>
304
+ <ContextMenuTrigger className="flex h-24 flex-col items-center justify-center rounded-lg border p-4">
305
+ <Icon symbol="description" className="text-2xl mb-2" />
306
+ <span className="text-sm">Document.pdf</span>
307
+ </ContextMenuTrigger>
308
+ <ContextMenuContent>
309
+ <ContextMenuItem>Open</ContextMenuItem>
310
+ <ContextMenuItem>Open with...</ContextMenuItem>
311
+ <ContextMenuSeparator />
312
+ <ContextMenuItem>
313
+ <Icon symbol="content_copy" />
314
+ Copy
315
+ </ContextMenuItem>
316
+ <ContextMenuItem variant="destructive">
317
+ <Icon symbol="delete" />
318
+ Delete
319
+ </ContextMenuItem>
320
+ </ContextMenuContent>
321
+ </ContextMenu>
322
+
323
+ <ContextMenu>
324
+ <ContextMenuTrigger className="flex h-24 flex-col items-center justify-center rounded-lg border p-4">
325
+ <Icon symbol="folder" className="text-2xl mb-2" />
326
+ <span className="text-sm">Projects</span>
327
+ </ContextMenuTrigger>
328
+ <ContextMenuContent>
329
+ <ContextMenuItem>Open</ContextMenuItem>
330
+ <ContextMenuItem>Open in new window</ContextMenuItem>
331
+ <ContextMenuSeparator />
332
+ <ContextMenuItem>
333
+ <Icon symbol="content_copy" />
334
+ Copy
335
+ </ContextMenuItem>
336
+ <ContextMenuItem variant="destructive">
337
+ <TrashIcon />
338
+ Delete
339
+ </ContextMenuItem>
340
+ </ContextMenuContent>
341
+ </ContextMenu>
342
+ </div>;
343
+ ```
344
+
345
+ ### Con Labels y Grupos
346
+
347
+ ```tsx
348
+ import { Icon } from "@adamosuiteservices/ui/icon";
349
+
350
+ <ContextMenu>
351
+ <ContextMenuTrigger>Right click</ContextMenuTrigger>
352
+ <ContextMenuContent className="w-56">
353
+ <ContextMenuLabel>File Operations</ContextMenuLabel>
354
+ <ContextMenuItem>
355
+ <Icon symbol="description" />
356
+ New File
357
+ <ContextMenuShortcut>⌘N</ContextMenuShortcut>
358
+ </ContextMenuItem>
359
+ <ContextMenuItem>
360
+ <Icon symbol="folder" />
361
+ New Folder
362
+ <ContextMenuShortcut>⌘⇧N</ContextMenuShortcut>
363
+ </ContextMenuItem>
364
+
365
+ <ContextMenuSeparator />
366
+
367
+ <ContextMenuLabel>Edit</ContextMenuLabel>
368
+ <ContextMenuItem>
369
+ <Icon symbol="content_copy" />
370
+ Copy
371
+ <ContextMenuShortcut>⌘C</ContextMenuShortcut>
372
+ </ContextMenuItem>
373
+ <ContextMenuItem>
374
+ <Icon symbol="edit" />
375
+ Paste
376
+ <ContextMenuShortcut>⌘V</ContextMenuShortcut>
377
+ </ContextMenuItem>
378
+ </ContextMenuContent>
379
+ </ContextMenu>;
380
+ ```
381
+
382
+ ### Items Deshabilitados
383
+
384
+ ```tsx
385
+ <ContextMenu>
386
+ <ContextMenuTrigger>Right click</ContextMenuTrigger>
387
+ <ContextMenuContent>
388
+ <ContextMenuItem>Available Action</ContextMenuItem>
389
+ <ContextMenuItem disabled>Disabled Action</ContextMenuItem>
390
+ <ContextMenuItem>Another Action</ContextMenuItem>
391
+ <ContextMenuSeparator />
392
+ <ContextMenuItem variant="destructive" disabled>
393
+ Cannot Delete
394
+ </ContextMenuItem>
395
+ </ContextMenuContent>
396
+ </ContextMenu>
397
+ ```
398
+
399
+ **Estilos disabled**: `opacity-50`, `pointer-events-none`, `data-[disabled=true]`
400
+
401
+ ### Con Callbacks
402
+
403
+ ```tsx
404
+ function App() {
405
+ const handleAction = (action: string) => {
406
+ console.log(`Action: ${action}`);
407
+ };
408
+
409
+ return (
410
+ <ContextMenu>
411
+ <ContextMenuTrigger>Right click</ContextMenuTrigger>
412
+ <ContextMenuContent>
413
+ <ContextMenuItem onSelect={() => handleAction("edit")}>
414
+ Edit
415
+ </ContextMenuItem>
416
+ <ContextMenuItem onSelect={() => handleAction("copy")}>
417
+ Copy
418
+ </ContextMenuItem>
419
+ <ContextMenuItem
420
+ variant="destructive"
421
+ onSelect={() => handleAction("delete")}
422
+ >
423
+ Delete
424
+ </ContextMenuItem>
425
+ </ContextMenuContent>
426
+ </ContextMenu>
427
+ );
428
+ }
429
+ ```
430
+
431
+ ### Con asChild Pattern
432
+
433
+ ```tsx
434
+ // Usar elemento custom como trigger sin wrapper adicional
435
+ <ContextMenu>
436
+ <ContextMenuTrigger asChild>
437
+ <button className="custom-button">Custom Trigger</button>
438
+ </ContextMenuTrigger>
439
+ <ContextMenuContent>
440
+ <ContextMenuItem>Action 1</ContextMenuItem>
441
+ <ContextMenuItem>Action 2</ContextMenuItem>
442
+ </ContextMenuContent>
443
+ </ContextMenu>
444
+ ```
445
+
446
+ ## Casos de Uso Comunes
447
+
448
+ **File managers**: Acciones contextuales en archivos/carpetas
449
+ **Image galleries**: Opciones sobre imágenes (download, share, delete)
450
+ **Text editors**: Copy, paste, format options
451
+ **Browser menus**: Back, forward, reload, more tools
452
+ **Data tables**: Row actions (edit, duplicate, delete)
453
+ **Canvas/diagram apps**: Acciones sobre elementos seleccionados
454
+
455
+ ## Estados y Data Attributes
456
+
457
+ ### ContextMenuItem States
458
+
459
+ - **Focus**: `data-[highlighted]` → `bg-accent`, `text-accent-foreground`
460
+ - **Disabled**: `data-[disabled=true]` → `opacity-50`, `pointer-events-none`
461
+ - **Destructive**: `data-[variant=destructive]` → `text-destructive`
462
+
463
+ ### ContextMenuSubTrigger States
464
+
465
+ - **Open**: `data-[state=open]` → `bg-accent`, `text-accent-foreground`
466
+
467
+ ### Content Animations
468
+
469
+ - **Opening**: `data-[state=open]` → `animate-in`, `fade-in-0`, `zoom-in-95`
470
+ - **Closing**: `data-[state=closed]` → `animate-out`, `fade-out-0`, `zoom-out-95`
471
+ - **Slide in**: `data-[side=top|right|bottom|left]` → direccional
472
+
473
+ ## Navegación por Teclado
474
+
475
+ - ✅ **Arrow Up/Down**: Navega entre items
476
+ - ✅ **Arrow Right**: Abre submenú (si disponible)
477
+ - ✅ **Arrow Left**: Cierra submenú
478
+ - ✅ **Enter/Space**: Activa item enfocado
479
+ - ✅ **Escape**: Cierra menú
480
+ - ✅ **Tab**: Mueve focus fuera del menú
481
+ - ✅ **Home/End**: Primer/último item (si `loop={true}`)
482
+ - ✅ **Type ahead**: Busca item por primera letra
483
+
484
+ ## Accesibilidad
485
+
486
+ - ✅ **ARIA**: `role="menu"` en content, `role="menuitem"` en items
487
+ - ✅ **Keyboard navigation**: Navegación completa por teclado
488
+ - ✅ **Focus management**: Focus visible y restaurado correctamente
489
+ - ✅ **Screen readers**: Anuncia items, estado de checkboxes/radios, disabled
490
+ - ✅ **Disabled items**: `aria-disabled="true"` en items deshabilitados
491
+ - ✅ **Submenus**: `aria-haspopup="menu"` en SubTriggers
492
+ - ✅ **Checked state**: `aria-checked` en CheckboxItem y RadioItem
493
+
494
+ ## Notas de Implementación
495
+
496
+ - **Basado en Radix UI**: `@radix-ui/react-context-menu`
497
+ - **Right click only**: Se activa solo con botón derecho del mouse
498
+ - **Auto positioning**: Portal con posicionamiento automático
499
+ - **Collision detection**: Evita salirse del viewport
500
+ - **Auto-close**: Cierra al seleccionar item (excepto checkboxes/radios)
501
+ - **Focus trap**: Mientras está abierto, focus queda dentro del menú
502
+ - **Portal rendering**: Content se renderiza en `document.body` por defecto
503
+ - **SVG auto-sizing**: Iconos automáticamente `size-4` y color muted
504
+ - **Inset support**: `inset` prop agrega `pl-8` para alinear con items con iconos
505
+ - **ChevronRightIcon**: Incluido automáticamente en SubTriggers
506
+ - **CheckIcon/CircleIcon**: Incluidos automáticamente en CheckboxItem/RadioItem
507
+ - **Variant destructive**: SVGs ignoran color muted y usan `text-destructive`
508
+
509
+ ## Posicionamiento
510
+
511
+ Por defecto, el menú aparece en la posición del cursor. Se puede personalizar:
512
+
513
+ ```tsx
514
+ <ContextMenuContent alignOffset={10} sideOffset={5} className="w-64">
515
+ {/* ... */}
516
+ </ContextMenuContent>
517
+ ```
518
+
519
+ **Opciones**:
520
+
521
+ - `alignOffset`: Ajusta alineamiento horizontal
522
+ - `sideOffset`: Distancia desde el cursor
523
+ - `className="w-*"`: Controla ancho del menú
524
+
525
+ ## Troubleshooting
526
+
527
+ **Menú no aparece al hacer clic derecho**: Verifica que estés usando `ContextMenuTrigger` correctamente y que no haya CSS que bloquee eventos
528
+ **Items no responden a click**: Asegúrate de usar `onSelect` en lugar de `onClick`
529
+ **Iconos mal alineados**: Usa `inset` en items sin iconos cuando otros sí tienen
530
+ **Menú no cierra al seleccionar**: CheckboxItems y RadioItems no cierran automáticamente (comportamiento esperado)
531
+ **Submenú no abre**: Verifica que estés usando `ContextMenuSub` + `ContextMenuSubTrigger` + `ContextMenuSubContent`
532
+ **Shortcuts no ejecutan**: `ContextMenuShortcut` es solo visual, implementa lógica en `onSelect`
533
+ **Focus se pierde al cerrar**: Usa `onCloseAutoFocus` para control custom del focus
534
+ **Posición incorrecta**: Verifica que el trigger no tenga `transform` CSS que afecte portales
535
+
536
+ ## Referencias
537
+
538
+ - **Radix UI Context Menu**: https://www.radix-ui.com/primitives/docs/components/context-menu
539
+ - **shadcn/ui Context Menu**: https://ui.shadcn.com/docs/components/context-menu
540
+ - **ARIA Menu Pattern**: https://www.w3.org/WAI/ARIA/apg/patterns/menu/