@adamosuiteservices/ui 2.11.15 → 2.11.17
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.
- package/dist/colors.css +1 -1
- package/dist/styles.css +1 -1
- package/dist/themes.css +1 -1
- package/docs/AI-GUIDE.md +321 -321
- package/docs/components/layout/sidebar.md +399 -399
- package/docs/components/layout/toaster.md +436 -436
- package/docs/components/ui/accordion-rounded.md +584 -584
- package/docs/components/ui/accordion.md +269 -269
- package/docs/components/ui/button-group.md +984 -984
- package/docs/components/ui/button.md +1137 -1137
- package/docs/components/ui/calendar.md +1159 -1159
- package/docs/components/ui/card.md +1455 -1455
- package/docs/components/ui/checkbox.md +292 -292
- package/docs/components/ui/collapsible.md +323 -323
- package/docs/components/ui/command.md +454 -454
- package/docs/components/ui/context-menu.md +540 -540
- package/docs/components/ui/dialog.md +628 -628
- package/docs/components/ui/dropdown-menu.md +709 -709
- package/docs/components/ui/field.md +706 -706
- package/docs/components/ui/hover-card.md +446 -446
- package/docs/components/ui/input.md +362 -362
- package/docs/components/ui/kbd.md +434 -434
- package/docs/components/ui/label.md +359 -359
- package/docs/components/ui/pagination.md +650 -650
- package/docs/components/ui/popover.md +536 -536
- package/docs/components/ui/progress.md +182 -182
- package/docs/components/ui/radio-group.md +311 -311
- package/docs/components/ui/select.md +352 -352
- package/docs/components/ui/separator.md +214 -214
- package/docs/components/ui/sheet.md +142 -142
- package/docs/components/ui/skeleton.md +140 -140
- package/docs/components/ui/slider.md +341 -341
- package/docs/components/ui/spinner.md +170 -170
- package/docs/components/ui/switch.md +408 -408
- package/docs/components/ui/tabs-underline.md +106 -106
- package/docs/components/ui/tabs.md +122 -122
- package/docs/components/ui/textarea.md +243 -243
- package/docs/components/ui/toggle.md +237 -237
- package/docs/components/ui/tooltip.md +317 -317
- package/docs/components/ui/typography.md +280 -280
- 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/
|