@delightui/components 0.1.105 → 0.1.107

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 (102) hide show
  1. package/README.md +104 -1
  2. package/dist/cjs/components/molecules/Modal/DemoModal.d.ts +8 -0
  3. package/dist/cjs/components/molecules/Modal/ModalContext/ModalContext.d.ts +41 -0
  4. package/dist/cjs/components/molecules/Modal/ModalContext/ModalContext.types.d.ts +87 -0
  5. package/dist/cjs/components/molecules/Modal/ModalContext/index.d.ts +3 -0
  6. package/dist/cjs/components/molecules/Modal/ModalContext/useModal.d.ts +34 -0
  7. package/dist/cjs/components/molecules/Modal/index.d.ts +2 -0
  8. package/dist/cjs/components/molecules/index.d.ts +2 -0
  9. package/dist/cjs/library.css +19 -6
  10. package/dist/cjs/library.js +3 -3
  11. package/dist/cjs/library.js.map +1 -1
  12. package/dist/esm/components/molecules/Modal/DemoModal.d.ts +8 -0
  13. package/dist/esm/components/molecules/Modal/ModalContext/ModalContext.d.ts +41 -0
  14. package/dist/esm/components/molecules/Modal/ModalContext/ModalContext.types.d.ts +87 -0
  15. package/dist/esm/components/molecules/Modal/ModalContext/index.d.ts +3 -0
  16. package/dist/esm/components/molecules/Modal/ModalContext/useModal.d.ts +34 -0
  17. package/dist/esm/components/molecules/Modal/index.d.ts +2 -0
  18. package/dist/esm/components/molecules/index.d.ts +2 -0
  19. package/dist/esm/library.css +19 -6
  20. package/dist/esm/library.js +3 -3
  21. package/dist/esm/library.js.map +1 -1
  22. package/dist/index.d.ts +108 -2
  23. package/docs/README.md +264 -0
  24. package/docs/components/atoms/ActionImage.md +119 -0
  25. package/docs/components/atoms/Button.md +197 -0
  26. package/docs/components/atoms/Checkbox.md +299 -0
  27. package/docs/components/atoms/CheckboxItem.md +314 -0
  28. package/docs/components/atoms/Chip.md +380 -0
  29. package/docs/components/atoms/CustomToggle.md +270 -0
  30. package/docs/components/atoms/Icon.md +365 -0
  31. package/docs/components/atoms/IconButton.md +407 -0
  32. package/docs/components/atoms/Image.md +448 -0
  33. package/docs/components/atoms/Input.md +430 -0
  34. package/docs/components/atoms/ListItem.md +502 -0
  35. package/docs/components/atoms/Password.md +472 -0
  36. package/docs/components/atoms/RadioButton.md +614 -0
  37. package/docs/components/atoms/RadioButtonItem.md +588 -0
  38. package/docs/components/atoms/ResponsiveComponent.md +612 -0
  39. package/docs/components/atoms/SelectListItem.md +609 -0
  40. package/docs/components/atoms/Slider.md +605 -0
  41. package/docs/components/atoms/Spinner.md +605 -0
  42. package/docs/components/atoms/Text.md +463 -0
  43. package/docs/components/atoms/TextArea.md +670 -0
  44. package/docs/components/atoms/ToastNotification.md +668 -0
  45. package/docs/components/atoms/Toggle.md +737 -0
  46. package/docs/components/atoms/ToggleButton.md +751 -0
  47. package/docs/components/atoms/Tooltip.md +391 -0
  48. package/docs/components/molecules/Accordion.md +440 -0
  49. package/docs/components/molecules/AccordionGroup.md +547 -0
  50. package/docs/components/molecules/ActionCard.md +546 -0
  51. package/docs/components/molecules/Breadcrumb.md +403 -0
  52. package/docs/components/molecules/Breadcrumbs.md +485 -0
  53. package/docs/components/molecules/ButtonGroup.md +383 -0
  54. package/docs/components/molecules/Card.md +298 -0
  55. package/docs/components/molecules/ChipInput.md +646 -0
  56. package/docs/components/molecules/ContextMenu.md +768 -0
  57. package/docs/components/molecules/CustomTimeSelector.md +116 -0
  58. package/docs/components/molecules/DatePicker.md +516 -0
  59. package/docs/components/molecules/DateTimeSelector.md +166 -0
  60. package/docs/components/molecules/FormField.md +312 -0
  61. package/docs/components/molecules/Grid.md +577 -0
  62. package/docs/components/molecules/GridItem.md +834 -0
  63. package/docs/components/molecules/GridList.md +244 -0
  64. package/docs/components/molecules/List.md +485 -0
  65. package/docs/components/molecules/Modal.md +470 -0
  66. package/docs/components/molecules/ModalFooter.md +702 -0
  67. package/docs/components/molecules/ModalHeader.md +756 -0
  68. package/docs/components/molecules/ModalProvider.md +205 -0
  69. package/docs/components/molecules/Nav.md +530 -0
  70. package/docs/components/molecules/NavItem.md +572 -0
  71. package/docs/components/molecules/NavLink.md +499 -0
  72. package/docs/components/molecules/Option.md +521 -0
  73. package/docs/components/molecules/Pagination.md +592 -0
  74. package/docs/components/molecules/PaginationNumberField.md +722 -0
  75. package/docs/components/molecules/Popover.md +516 -0
  76. package/docs/components/molecules/ProgressBar.md +624 -0
  77. package/docs/components/molecules/RadioGroup.md +831 -0
  78. package/docs/components/molecules/RepeaterList.md +185 -0
  79. package/docs/components/molecules/Select.md +402 -0
  80. package/docs/components/molecules/SortableTrigger.md +82 -0
  81. package/docs/components/molecules/useModal.md +379 -0
  82. package/docs/components/organisms/Dropzone.md +346 -0
  83. package/docs/components/organisms/DropzoneClear.md +135 -0
  84. package/docs/components/organisms/DropzoneContent.md +216 -0
  85. package/docs/components/organisms/DropzoneFilename.md +191 -0
  86. package/docs/components/organisms/DropzoneSupportedFormats.md +184 -0
  87. package/docs/components/organisms/DropzoneTrigger.md +209 -0
  88. package/docs/components/organisms/Form.md +533 -0
  89. package/docs/components/organisms/SlideOutPanel.md +662 -0
  90. package/docs/components/organisms/TabContent.md +902 -0
  91. package/docs/components/organisms/TabItem.md +1091 -0
  92. package/docs/components/organisms/Table.md +611 -0
  93. package/docs/components/organisms/TableBody.md +679 -0
  94. package/docs/components/organisms/TableCell.md +482 -0
  95. package/docs/components/organisms/TableHeader.md +513 -0
  96. package/docs/components/organisms/TableHeaderCell.md +661 -0
  97. package/docs/components/organisms/TableRow.md +715 -0
  98. package/docs/components/organisms/Tabs.md +1330 -0
  99. package/docs/components/utils/ConditionalView.md +568 -0
  100. package/docs/components/utils/RenderStateView.md +726 -0
  101. package/docs/components/utils/WrapTextNodes.md +614 -0
  102. package/package.json +3 -2
@@ -0,0 +1,768 @@
1
+ # ContextMenu
2
+
3
+ ## Description
4
+
5
+ A right-click context menu component that displays a list of actions when triggered by a right-click or programmatic event. Built on top of the List component, it provides contextual actions and navigation options with keyboard support and proper positioning relative to the trigger point.
6
+
7
+ ## Aliases
8
+
9
+ - ContextMenu
10
+ - RightClickMenu
11
+ - ContextualMenu
12
+ - PopupMenu
13
+ - ActionMenu
14
+
15
+ ## Props Breakdown
16
+
17
+ **Extends:** ListProps<T> (inherits all List component properties)
18
+
19
+ | Prop | Type | Default | Required | Description |
20
+ |------|------|---------|----------|-------------|
21
+ | `items` | `T[]` | - | Yes | Array of menu items to display |
22
+ | `onSelect` | `(item: T) => void` | - | No | Callback fired when a menu item is selected |
23
+ | `trigger` | `ReactNode` | - | No | Element that triggers the context menu |
24
+ | `position` | `{ x: number, y: number }` | - | No | Position coordinates for the menu |
25
+ | `visible` | `boolean` | `false` | No | Controls menu visibility |
26
+ | `onClose` | `() => void` | - | No | Callback fired when menu should close |
27
+ | `className` | `string` | - | No | Additional CSS class names |
28
+ | `component-variant` | `string` | - | No | Override styling variant |
29
+
30
+ ## Examples
31
+
32
+ ### Basic Context Menu
33
+ ```tsx
34
+ import { ContextMenu, ListItem } from '@delightui/components';
35
+
36
+ function BasicExample() {
37
+ const [menuVisible, setMenuVisible] = useState(false);
38
+ const [menuPosition, setMenuPosition] = useState({ x: 0, y: 0 });
39
+
40
+ const menuItems = [
41
+ { id: '1', label: 'Copy', icon: 'Copy' },
42
+ { id: '2', label: 'Paste', icon: 'Paste' },
43
+ { id: '3', label: 'Delete', icon: 'Delete' }
44
+ ];
45
+
46
+ const handleRightClick = (event) => {
47
+ event.preventDefault();
48
+ setMenuPosition({ x: event.clientX, y: event.clientY });
49
+ setMenuVisible(true);
50
+ };
51
+
52
+ const handleSelect = (item) => {
53
+ console.log('Selected:', item.label);
54
+ setMenuVisible(false);
55
+ };
56
+
57
+ return (
58
+ <div>
59
+ <div
60
+ onContextMenu={handleRightClick}
61
+ style={{ padding: '50px', border: '1px dashed #ccc' }}
62
+ >
63
+ Right-click here for context menu
64
+ </div>
65
+
66
+ <ContextMenu
67
+ items={menuItems}
68
+ visible={menuVisible}
69
+ position={menuPosition}
70
+ onSelect={handleSelect}
71
+ onClose={() => setMenuVisible(false)}
72
+ />
73
+ </div>
74
+ );
75
+ }
76
+ ```
77
+
78
+ ### File Manager Context Menu
79
+ ```tsx
80
+ function FileManagerExample() {
81
+ const [selectedFile, setSelectedFile] = useState(null);
82
+ const [menuVisible, setMenuVisible] = useState(false);
83
+ const [menuPosition, setMenuPosition] = useState({ x: 0, y: 0 });
84
+
85
+ const files = [
86
+ { id: '1', name: 'Document.pdf', type: 'pdf' },
87
+ { id: '2', name: 'Image.jpg', type: 'image' },
88
+ { id: '3', name: 'Spreadsheet.xlsx', type: 'excel' }
89
+ ];
90
+
91
+ const getContextMenuItems = (file) => {
92
+ const baseItems = [
93
+ { id: 'open', label: 'Open', icon: 'Open' },
94
+ { id: 'rename', label: 'Rename', icon: 'Edit' },
95
+ { id: 'copy', label: 'Copy', icon: 'Copy' },
96
+ { id: 'separator1', type: 'separator' },
97
+ { id: 'delete', label: 'Delete', icon: 'Delete', style: 'destructive' }
98
+ ];
99
+
100
+ if (file.type === 'image') {
101
+ baseItems.splice(1, 0, { id: 'preview', label: 'Preview', icon: 'Eye' });
102
+ }
103
+
104
+ return baseItems;
105
+ };
106
+
107
+ const handleFileRightClick = (event, file) => {
108
+ event.preventDefault();
109
+ setSelectedFile(file);
110
+ setMenuPosition({ x: event.clientX, y: event.clientY });
111
+ setMenuVisible(true);
112
+ };
113
+
114
+ const handleMenuSelect = (item) => {
115
+ if (!selectedFile) return;
116
+
117
+ switch (item.id) {
118
+ case 'open':
119
+ console.log('Opening:', selectedFile.name);
120
+ break;
121
+ case 'rename':
122
+ // Show rename dialog
123
+ break;
124
+ case 'copy':
125
+ navigator.clipboard.writeText(selectedFile.name);
126
+ break;
127
+ case 'preview':
128
+ // Show image preview
129
+ break;
130
+ case 'delete':
131
+ // Show delete confirmation
132
+ break;
133
+ }
134
+
135
+ setMenuVisible(false);
136
+ setSelectedFile(null);
137
+ };
138
+
139
+ return (
140
+ <div className="file-manager">
141
+ {files.map(file => (
142
+ <div
143
+ key={file.id}
144
+ className="file-item"
145
+ onContextMenu={(e) => handleFileRightClick(e, file)}
146
+ >
147
+ <Icon icon={file.type === 'pdf' ? 'FilePdf' : file.type === 'image' ? 'FileImage' : 'FileExcel'} />
148
+ <Text>{file.name}</Text>
149
+ </div>
150
+ ))}
151
+
152
+ <ContextMenu
153
+ items={selectedFile ? getContextMenuItems(selectedFile) : []}
154
+ visible={menuVisible}
155
+ position={menuPosition}
156
+ onSelect={handleMenuSelect}
157
+ onClose={() => {
158
+ setMenuVisible(false);
159
+ setSelectedFile(null);
160
+ }}
161
+ />
162
+ </div>
163
+ );
164
+ }
165
+ ```
166
+
167
+ ### Table Row Context Menu
168
+ ```tsx
169
+ function TableContextExample() {
170
+ const [users] = useState([
171
+ { id: 1, name: 'John Doe', email: 'john@example.com', role: 'Admin' },
172
+ { id: 2, name: 'Jane Smith', email: 'jane@example.com', role: 'User' },
173
+ { id: 3, name: 'Bob Johnson', email: 'bob@example.com', role: 'User' }
174
+ ]);
175
+
176
+ const [contextMenu, setContextMenu] = useState({
177
+ visible: false,
178
+ position: { x: 0, y: 0 },
179
+ user: null
180
+ });
181
+
182
+ const getMenuItems = (user) => [
183
+ { id: 'view', label: 'View Profile', icon: 'Person' },
184
+ { id: 'edit', label: 'Edit User', icon: 'Edit' },
185
+ { id: 'separator1', type: 'separator' },
186
+ { id: 'promote', label: user.role === 'Admin' ? 'Demote to User' : 'Promote to Admin', icon: 'Star' },
187
+ { id: 'separator2', type: 'separator' },
188
+ { id: 'delete', label: 'Delete User', icon: 'Delete', style: 'destructive' }
189
+ ];
190
+
191
+ const handleRowRightClick = (event, user) => {
192
+ event.preventDefault();
193
+ setContextMenu({
194
+ visible: true,
195
+ position: { x: event.clientX, y: event.clientY },
196
+ user
197
+ });
198
+ };
199
+
200
+ const handleMenuSelect = (item) => {
201
+ const { user } = contextMenu;
202
+
203
+ switch (item.id) {
204
+ case 'view':
205
+ console.log('Viewing profile for:', user.name);
206
+ break;
207
+ case 'edit':
208
+ console.log('Editing user:', user.name);
209
+ break;
210
+ case 'promote':
211
+ console.log('Changing role for:', user.name);
212
+ break;
213
+ case 'delete':
214
+ console.log('Deleting user:', user.name);
215
+ break;
216
+ }
217
+
218
+ setContextMenu({ visible: false, position: { x: 0, y: 0 }, user: null });
219
+ };
220
+
221
+ return (
222
+ <div className="user-table">
223
+ <table>
224
+ <thead>
225
+ <tr>
226
+ <th>Name</th>
227
+ <th>Email</th>
228
+ <th>Role</th>
229
+ </tr>
230
+ </thead>
231
+ <tbody>
232
+ {users.map(user => (
233
+ <tr
234
+ key={user.id}
235
+ onContextMenu={(e) => handleRowRightClick(e, user)}
236
+ className="table-row"
237
+ >
238
+ <td>{user.name}</td>
239
+ <td>{user.email}</td>
240
+ <td>{user.role}</td>
241
+ </tr>
242
+ ))}
243
+ </tbody>
244
+ </table>
245
+
246
+ <ContextMenu
247
+ items={contextMenu.user ? getMenuItems(contextMenu.user) : []}
248
+ visible={contextMenu.visible}
249
+ position={contextMenu.position}
250
+ onSelect={handleMenuSelect}
251
+ onClose={() => setContextMenu({ visible: false, position: { x: 0, y: 0 }, user: null })}
252
+ />
253
+ </div>
254
+ );
255
+ }
256
+ ```
257
+
258
+ ### Canvas Context Menu
259
+ ```tsx
260
+ function CanvasContextExample() {
261
+ const [shapes, setShapes] = useState([
262
+ { id: '1', type: 'rectangle', x: 50, y: 50, selected: false },
263
+ { id: '2', type: 'circle', x: 150, y: 100, selected: false }
264
+ ]);
265
+
266
+ const [contextMenu, setContextMenu] = useState({
267
+ visible: false,
268
+ position: { x: 0, y: 0 },
269
+ target: null
270
+ });
271
+
272
+ const getCanvasMenuItems = () => [
273
+ { id: 'paste', label: 'Paste', icon: 'Paste' },
274
+ { id: 'separator1', type: 'separator' },
275
+ { id: 'add-rectangle', label: 'Add Rectangle', icon: 'Rectangle' },
276
+ { id: 'add-circle', label: 'Add Circle', icon: 'Circle' },
277
+ { id: 'separator2', type: 'separator' },
278
+ { id: 'clear-all', label: 'Clear All', icon: 'Delete', style: 'destructive' }
279
+ ];
280
+
281
+ const getShapeMenuItems = () => [
282
+ { id: 'copy', label: 'Copy', icon: 'Copy' },
283
+ { id: 'duplicate', label: 'Duplicate', icon: 'Duplicate' },
284
+ { id: 'separator1', type: 'separator' },
285
+ { id: 'bring-front', label: 'Bring to Front', icon: 'ArrowUp' },
286
+ { id: 'send-back', label: 'Send to Back', icon: 'ArrowDown' },
287
+ { id: 'separator2', type: 'separator' },
288
+ { id: 'delete', label: 'Delete', icon: 'Delete', style: 'destructive' }
289
+ ];
290
+
291
+ const handleCanvasRightClick = (event) => {
292
+ event.preventDefault();
293
+ setContextMenu({
294
+ visible: true,
295
+ position: { x: event.clientX, y: event.clientY },
296
+ target: 'canvas'
297
+ });
298
+ };
299
+
300
+ const handleShapeRightClick = (event, shape) => {
301
+ event.preventDefault();
302
+ event.stopPropagation();
303
+ setContextMenu({
304
+ visible: true,
305
+ position: { x: event.clientX, y: event.clientY },
306
+ target: shape
307
+ });
308
+ };
309
+
310
+ const handleMenuSelect = (item) => {
311
+ if (contextMenu.target === 'canvas') {
312
+ switch (item.id) {
313
+ case 'add-rectangle':
314
+ const newRect = {
315
+ id: Date.now().toString(),
316
+ type: 'rectangle',
317
+ x: contextMenu.position.x,
318
+ y: contextMenu.position.y,
319
+ selected: false
320
+ };
321
+ setShapes(prev => [...prev, newRect]);
322
+ break;
323
+ case 'clear-all':
324
+ setShapes([]);
325
+ break;
326
+ }
327
+ } else {
328
+ // Handle shape-specific actions
329
+ switch (item.id) {
330
+ case 'delete':
331
+ setShapes(prev => prev.filter(s => s.id !== contextMenu.target.id));
332
+ break;
333
+ case 'duplicate':
334
+ const duplicate = {
335
+ ...contextMenu.target,
336
+ id: Date.now().toString(),
337
+ x: contextMenu.target.x + 20,
338
+ y: contextMenu.target.y + 20
339
+ };
340
+ setShapes(prev => [...prev, duplicate]);
341
+ break;
342
+ }
343
+ }
344
+
345
+ setContextMenu({ visible: false, position: { x: 0, y: 0 }, target: null });
346
+ };
347
+
348
+ return (
349
+ <div className="canvas-container">
350
+ <div
351
+ className="canvas"
352
+ onContextMenu={handleCanvasRightClick}
353
+ style={{ width: '400px', height: '300px', border: '1px solid #ccc', position: 'relative' }}
354
+ >
355
+ {shapes.map(shape => (
356
+ <div
357
+ key={shape.id}
358
+ className={`shape ${shape.type}`}
359
+ onContextMenu={(e) => handleShapeRightClick(e, shape)}
360
+ style={{
361
+ position: 'absolute',
362
+ left: shape.x,
363
+ top: shape.y,
364
+ width: '50px',
365
+ height: '50px',
366
+ backgroundColor: shape.type === 'rectangle' ? '#4CAF50' : '#2196F3',
367
+ borderRadius: shape.type === 'circle' ? '50%' : '0'
368
+ }}
369
+ />
370
+ ))}
371
+ </div>
372
+
373
+ <ContextMenu
374
+ items={contextMenu.target === 'canvas' ? getCanvasMenuItems() : getShapeMenuItems()}
375
+ visible={contextMenu.visible}
376
+ position={contextMenu.position}
377
+ onSelect={handleMenuSelect}
378
+ onClose={() => setContextMenu({ visible: false, position: { x: 0, y: 0 }, target: null })}
379
+ />
380
+ </div>
381
+ );
382
+ }
383
+ ```
384
+
385
+ ### Text Editor Context Menu
386
+ ```tsx
387
+ function TextEditorContextExample() {
388
+ const [selectedText, setSelectedText] = useState('');
389
+ const [contextMenu, setContextMenu] = useState({
390
+ visible: false,
391
+ position: { x: 0, y: 0 }
392
+ });
393
+
394
+ const getTextMenuItems = () => {
395
+ const hasSelection = selectedText.length > 0;
396
+
397
+ return [
398
+ { id: 'cut', label: 'Cut', icon: 'Cut', disabled: !hasSelection },
399
+ { id: 'copy', label: 'Copy', icon: 'Copy', disabled: !hasSelection },
400
+ { id: 'paste', label: 'Paste', icon: 'Paste' },
401
+ { id: 'separator1', type: 'separator' },
402
+ { id: 'select-all', label: 'Select All', icon: 'SelectAll' },
403
+ { id: 'separator2', type: 'separator' },
404
+ { id: 'bold', label: 'Bold', icon: 'Bold', disabled: !hasSelection },
405
+ { id: 'italic', label: 'Italic', icon: 'Italic', disabled: !hasSelection },
406
+ { id: 'underline', label: 'Underline', icon: 'Underline', disabled: !hasSelection }
407
+ ];
408
+ };
409
+
410
+ const handleTextAreaRightClick = (event) => {
411
+ event.preventDefault();
412
+ const textarea = event.target;
413
+ const selection = window.getSelection()?.toString() || '';
414
+ setSelectedText(selection);
415
+ setContextMenu({
416
+ visible: true,
417
+ position: { x: event.clientX, y: event.clientY }
418
+ });
419
+ };
420
+
421
+ const handleMenuSelect = (item) => {
422
+ switch (item.id) {
423
+ case 'cut':
424
+ document.execCommand('cut');
425
+ break;
426
+ case 'copy':
427
+ document.execCommand('copy');
428
+ break;
429
+ case 'paste':
430
+ document.execCommand('paste');
431
+ break;
432
+ case 'select-all':
433
+ document.execCommand('selectAll');
434
+ break;
435
+ case 'bold':
436
+ document.execCommand('bold');
437
+ break;
438
+ case 'italic':
439
+ document.execCommand('italic');
440
+ break;
441
+ case 'underline':
442
+ document.execCommand('underline');
443
+ break;
444
+ }
445
+
446
+ setContextMenu({ visible: false, position: { x: 0, y: 0 } });
447
+ };
448
+
449
+ return (
450
+ <div className="text-editor">
451
+ <div
452
+ contentEditable
453
+ className="editor-content"
454
+ onContextMenu={handleTextAreaRightClick}
455
+ style={{
456
+ border: '1px solid #ccc',
457
+ padding: '10px',
458
+ minHeight: '200px',
459
+ outline: 'none'
460
+ }}
461
+ >
462
+ Right-click anywhere in this text editor to see formatting options.
463
+ Select some text first to enable cut, copy, and formatting commands.
464
+ </div>
465
+
466
+ <ContextMenu
467
+ items={getTextMenuItems()}
468
+ visible={contextMenu.visible}
469
+ position={contextMenu.position}
470
+ onSelect={handleMenuSelect}
471
+ onClose={() => setContextMenu({ visible: false, position: { x: 0, y: 0 } })}
472
+ />
473
+ </div>
474
+ );
475
+ }
476
+ ```
477
+
478
+ ### Multi-Level Context Menu
479
+ ```tsx
480
+ function MultiLevelContextExample() {
481
+ const [contextMenu, setContextMenu] = useState({
482
+ visible: false,
483
+ position: { x: 0, y: 0 },
484
+ level: 'main'
485
+ });
486
+
487
+ const getMainMenuItems = () => [
488
+ { id: 'new', label: 'New', icon: 'Add', hasSubmenu: true },
489
+ { id: 'edit', label: 'Edit', icon: 'Edit' },
490
+ { id: 'view', label: 'View', icon: 'Eye', hasSubmenu: true },
491
+ { id: 'separator1', type: 'separator' },
492
+ { id: 'delete', label: 'Delete', icon: 'Delete', style: 'destructive' }
493
+ ];
494
+
495
+ const getNewSubmenuItems = () => [
496
+ { id: 'new-file', label: 'File', icon: 'File' },
497
+ { id: 'new-folder', label: 'Folder', icon: 'Folder' },
498
+ { id: 'new-project', label: 'Project', icon: 'Project' },
499
+ { id: 'separator1', type: 'separator' },
500
+ { id: 'back', label: 'Back', icon: 'ArrowBack' }
501
+ ];
502
+
503
+ const getViewSubmenuItems = () => [
504
+ { id: 'view-list', label: 'List View', icon: 'List' },
505
+ { id: 'view-grid', label: 'Grid View', icon: 'Grid' },
506
+ { id: 'view-details', label: 'Details View', icon: 'Details' },
507
+ { id: 'separator1', type: 'separator' },
508
+ { id: 'back', label: 'Back', icon: 'ArrowBack' }
509
+ ];
510
+
511
+ const getCurrentItems = () => {
512
+ switch (contextMenu.level) {
513
+ case 'new':
514
+ return getNewSubmenuItems();
515
+ case 'view':
516
+ return getViewSubmenuItems();
517
+ default:
518
+ return getMainMenuItems();
519
+ }
520
+ };
521
+
522
+ const handleRightClick = (event) => {
523
+ event.preventDefault();
524
+ setContextMenu({
525
+ visible: true,
526
+ position: { x: event.clientX, y: event.clientY },
527
+ level: 'main'
528
+ });
529
+ };
530
+
531
+ const handleMenuSelect = (item) => {
532
+ if (item.hasSubmenu) {
533
+ setContextMenu(prev => ({ ...prev, level: item.id }));
534
+ return;
535
+ }
536
+
537
+ if (item.id === 'back') {
538
+ setContextMenu(prev => ({ ...prev, level: 'main' }));
539
+ return;
540
+ }
541
+
542
+ console.log('Selected:', item.label);
543
+ setContextMenu({ visible: false, position: { x: 0, y: 0 }, level: 'main' });
544
+ };
545
+
546
+ return (
547
+ <div>
548
+ <div
549
+ onContextMenu={handleRightClick}
550
+ style={{
551
+ padding: '100px',
552
+ border: '2px dashed #ccc',
553
+ textAlign: 'center'
554
+ }}
555
+ >
556
+ Right-click for multi-level context menu
557
+ </div>
558
+
559
+ <ContextMenu
560
+ items={getCurrentItems()}
561
+ visible={contextMenu.visible}
562
+ position={contextMenu.position}
563
+ onSelect={handleMenuSelect}
564
+ onClose={() => setContextMenu({ visible: false, position: { x: 0, y: 0 }, level: 'main' })}
565
+ />
566
+ </div>
567
+ );
568
+ }
569
+ ```
570
+
571
+ ### Image Gallery Context Menu
572
+ ```tsx
573
+ function ImageGalleryContextExample() {
574
+ const [images] = useState([
575
+ { id: '1', src: '/image1.jpg', name: 'Sunset.jpg', favorite: false },
576
+ { id: '2', src: '/image2.jpg', name: 'Mountains.jpg', favorite: true },
577
+ { id: '3', src: '/image3.jpg', name: 'Ocean.jpg', favorite: false }
578
+ ]);
579
+
580
+ const [contextMenu, setContextMenu] = useState({
581
+ visible: false,
582
+ position: { x: 0, y: 0 },
583
+ image: null
584
+ });
585
+
586
+ const getImageMenuItems = (image) => [
587
+ { id: 'view', label: 'View Full Size', icon: 'Eye' },
588
+ { id: 'favorite', label: image.favorite ? 'Remove from Favorites' : 'Add to Favorites', icon: image.favorite ? 'StarFilled' : 'Star' },
589
+ { id: 'separator1', type: 'separator' },
590
+ { id: 'copy', label: 'Copy Image', icon: 'Copy' },
591
+ { id: 'copy-link', label: 'Copy Link', icon: 'Link' },
592
+ { id: 'separator2', type: 'separator' },
593
+ { id: 'rename', label: 'Rename', icon: 'Edit' },
594
+ { id: 'download', label: 'Download', icon: 'Download' },
595
+ { id: 'separator3', type: 'separator' },
596
+ { id: 'delete', label: 'Delete', icon: 'Delete', style: 'destructive' }
597
+ ];
598
+
599
+ const handleImageRightClick = (event, image) => {
600
+ event.preventDefault();
601
+ setContextMenu({
602
+ visible: true,
603
+ position: { x: event.clientX, y: event.clientY },
604
+ image
605
+ });
606
+ };
607
+
608
+ const handleMenuSelect = (item) => {
609
+ const { image } = contextMenu;
610
+
611
+ switch (item.id) {
612
+ case 'view':
613
+ console.log('Viewing full size:', image.name);
614
+ break;
615
+ case 'favorite':
616
+ console.log(image.favorite ? 'Removing from favorites:' : 'Adding to favorites:', image.name);
617
+ break;
618
+ case 'copy':
619
+ console.log('Copying image:', image.name);
620
+ break;
621
+ case 'copy-link':
622
+ navigator.clipboard.writeText(image.src);
623
+ break;
624
+ case 'rename':
625
+ console.log('Renaming:', image.name);
626
+ break;
627
+ case 'download':
628
+ console.log('Downloading:', image.name);
629
+ break;
630
+ case 'delete':
631
+ console.log('Deleting:', image.name);
632
+ break;
633
+ }
634
+
635
+ setContextMenu({ visible: false, position: { x: 0, y: 0 }, image: null });
636
+ };
637
+
638
+ return (
639
+ <div className="image-gallery">
640
+ <div className="gallery-grid">
641
+ {images.map(image => (
642
+ <div
643
+ key={image.id}
644
+ className="gallery-item"
645
+ onContextMenu={(e) => handleImageRightClick(e, image)}
646
+ >
647
+ <Image src={image.src} alt={image.name} />
648
+ <div className="image-info">
649
+ <Text>{image.name}</Text>
650
+ {image.favorite && <Icon icon="Star" className="favorite-icon" />}
651
+ </div>
652
+ </div>
653
+ ))}
654
+ </div>
655
+
656
+ <ContextMenu
657
+ items={contextMenu.image ? getImageMenuItems(contextMenu.image) : []}
658
+ visible={contextMenu.visible}
659
+ position={contextMenu.position}
660
+ onSelect={handleMenuSelect}
661
+ onClose={() => setContextMenu({ visible: false, position: { x: 0, y: 0 }, image: null })}
662
+ />
663
+ </div>
664
+ );
665
+ }
666
+ ```
667
+
668
+ ### Tree View Context Menu
669
+ ```tsx
670
+ function TreeViewContextExample() {
671
+ const [treeData] = useState([
672
+ {
673
+ id: '1',
674
+ name: 'Documents',
675
+ type: 'folder',
676
+ children: [
677
+ { id: '1-1', name: 'Resume.pdf', type: 'file' },
678
+ { id: '1-2', name: 'Cover Letter.docx', type: 'file' }
679
+ ]
680
+ },
681
+ {
682
+ id: '2',
683
+ name: 'Images',
684
+ type: 'folder',
685
+ children: [
686
+ { id: '2-1', name: 'Vacation.jpg', type: 'file' }
687
+ ]
688
+ }
689
+ ]);
690
+
691
+ const [contextMenu, setContextMenu] = useState({
692
+ visible: false,
693
+ position: { x: 0, y: 0 },
694
+ node: null
695
+ });
696
+
697
+ const getFolderMenuItems = () => [
698
+ { id: 'open', label: 'Open', icon: 'FolderOpen' },
699
+ { id: 'separator1', type: 'separator' },
700
+ { id: 'new-folder', label: 'New Folder', icon: 'FolderAdd' },
701
+ { id: 'new-file', label: 'New File', icon: 'FileAdd' },
702
+ { id: 'separator2', type: 'separator' },
703
+ { id: 'rename', label: 'Rename', icon: 'Edit' },
704
+ { id: 'copy', label: 'Copy', icon: 'Copy' },
705
+ { id: 'separator3', type: 'separator' },
706
+ { id: 'delete', label: 'Delete', icon: 'Delete', style: 'destructive' }
707
+ ];
708
+
709
+ const getFileMenuItems = () => [
710
+ { id: 'open', label: 'Open', icon: 'Open' },
711
+ { id: 'open-with', label: 'Open With...', icon: 'Apps' },
712
+ { id: 'separator1', type: 'separator' },
713
+ { id: 'rename', label: 'Rename', icon: 'Edit' },
714
+ { id: 'copy', label: 'Copy', icon: 'Copy' },
715
+ { id: 'cut', label: 'Cut', icon: 'Cut' },
716
+ { id: 'separator2', type: 'separator' },
717
+ { id: 'properties', label: 'Properties', icon: 'Info' },
718
+ { id: 'separator3', type: 'separator' },
719
+ { id: 'delete', label: 'Delete', icon: 'Delete', style: 'destructive' }
720
+ ];
721
+
722
+ const handleNodeRightClick = (event, node) => {
723
+ event.preventDefault();
724
+ event.stopPropagation();
725
+ setContextMenu({
726
+ visible: true,
727
+ position: { x: event.clientX, y: event.clientY },
728
+ node
729
+ });
730
+ };
731
+
732
+ const handleMenuSelect = (item) => {
733
+ const { node } = contextMenu;
734
+ console.log(`${item.label} on ${node.type}: ${node.name}`);
735
+ setContextMenu({ visible: false, position: { x: 0, y: 0 }, node: null });
736
+ };
737
+
738
+ const renderTreeNode = (node, level = 0) => (
739
+ <div key={node.id} style={{ marginLeft: `${level * 20}px` }}>
740
+ <div
741
+ className="tree-node"
742
+ onContextMenu={(e) => handleNodeRightClick(e, node)}
743
+ style={{ padding: '4px', cursor: 'pointer' }}
744
+ >
745
+ <Icon icon={node.type === 'folder' ? 'Folder' : 'File'} />
746
+ <Text>{node.name}</Text>
747
+ </div>
748
+ {node.children?.map(child => renderTreeNode(child, level + 1))}
749
+ </div>
750
+ );
751
+
752
+ return (
753
+ <div className="tree-view">
754
+ <div className="tree-container">
755
+ {treeData.map(node => renderTreeNode(node))}
756
+ </div>
757
+
758
+ <ContextMenu
759
+ items={contextMenu.node?.type === 'folder' ? getFolderMenuItems() : getFileMenuItems()}
760
+ visible={contextMenu.visible}
761
+ position={contextMenu.position}
762
+ onSelect={handleMenuSelect}
763
+ onClose={() => setContextMenu({ visible: false, position: { x: 0, y: 0 }, node: null })}
764
+ />
765
+ </div>
766
+ );
767
+ }
768
+ ```