ywana-core8 0.2.17 → 0.2.18
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/index.css +231 -0
- package/dist/index.js +218 -2
- package/dist/index.js.map +1 -1
- package/dist/index.modern.js +218 -2
- package/dist/index.modern.js.map +1 -1
- package/dist/index.umd.js +218 -2
- package/dist/index.umd.js.map +1 -1
- package/package.json +1 -1
- package/src/desktop/Desktop.stories.jsx +522 -14
- package/src/desktop/window.css +231 -0
- package/src/desktop/window.js +341 -8
package/src/desktop/window.css
CHANGED
@@ -11,6 +11,7 @@
|
|
11
11
|
overflow: hidden;
|
12
12
|
user-select: none;
|
13
13
|
pointer-events: auto; /* Ensure windows are clickable */
|
14
|
+
position: relative; /* For floating elements positioning */
|
14
15
|
}
|
15
16
|
|
16
17
|
.window:focus-within {
|
@@ -256,6 +257,236 @@
|
|
256
257
|
user-select: none !important;
|
257
258
|
}
|
258
259
|
|
260
|
+
/* Window Instance Context - Floating Elements */
|
261
|
+
.window-floating-elements {
|
262
|
+
position: absolute;
|
263
|
+
top: 0;
|
264
|
+
left: 0;
|
265
|
+
right: 0;
|
266
|
+
bottom: 0;
|
267
|
+
pointer-events: none;
|
268
|
+
z-index: 1000;
|
269
|
+
}
|
270
|
+
|
271
|
+
/* Dialog Styles */
|
272
|
+
.window-dialog {
|
273
|
+
position: absolute;
|
274
|
+
top: 0;
|
275
|
+
left: 0;
|
276
|
+
right: 0;
|
277
|
+
bottom: 0;
|
278
|
+
background: rgba(0, 0, 0, 0.3);
|
279
|
+
display: flex;
|
280
|
+
align-items: center;
|
281
|
+
justify-content: center;
|
282
|
+
pointer-events: auto;
|
283
|
+
z-index: 1002;
|
284
|
+
}
|
285
|
+
|
286
|
+
.window-dialog__content {
|
287
|
+
background: white;
|
288
|
+
border-radius: 8px;
|
289
|
+
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
|
290
|
+
min-width: 300px;
|
291
|
+
max-width: 80%;
|
292
|
+
max-height: 80%;
|
293
|
+
overflow: hidden;
|
294
|
+
display: flex;
|
295
|
+
flex-direction: column;
|
296
|
+
}
|
297
|
+
|
298
|
+
.window-dialog__header {
|
299
|
+
padding: 16px 20px;
|
300
|
+
border-bottom: 1px solid #e0e0e0;
|
301
|
+
display: flex;
|
302
|
+
align-items: center;
|
303
|
+
justify-content: space-between;
|
304
|
+
background: #f8f9fa;
|
305
|
+
}
|
306
|
+
|
307
|
+
.window-dialog__title {
|
308
|
+
margin: 0;
|
309
|
+
font-size: 16px;
|
310
|
+
font-weight: 600;
|
311
|
+
color: #333;
|
312
|
+
}
|
313
|
+
|
314
|
+
.window-dialog__close {
|
315
|
+
background: none;
|
316
|
+
border: none;
|
317
|
+
font-size: 18px;
|
318
|
+
cursor: pointer;
|
319
|
+
color: #666;
|
320
|
+
width: 24px;
|
321
|
+
height: 24px;
|
322
|
+
display: flex;
|
323
|
+
align-items: center;
|
324
|
+
justify-content: center;
|
325
|
+
border-radius: 4px;
|
326
|
+
}
|
327
|
+
|
328
|
+
.window-dialog__close:hover {
|
329
|
+
background: rgba(0, 0, 0, 0.1);
|
330
|
+
color: #333;
|
331
|
+
}
|
332
|
+
|
333
|
+
.window-dialog__body {
|
334
|
+
padding: 20px;
|
335
|
+
flex: 1;
|
336
|
+
overflow: auto;
|
337
|
+
}
|
338
|
+
|
339
|
+
.window-dialog__footer {
|
340
|
+
padding: 16px 20px;
|
341
|
+
border-top: 1px solid #e0e0e0;
|
342
|
+
display: flex;
|
343
|
+
gap: 8px;
|
344
|
+
justify-content: flex-end;
|
345
|
+
background: #f8f9fa;
|
346
|
+
}
|
347
|
+
|
348
|
+
/* Notification Styles */
|
349
|
+
.window-notification {
|
350
|
+
position: absolute;
|
351
|
+
top: 16px;
|
352
|
+
right: 16px;
|
353
|
+
background: white;
|
354
|
+
border-radius: 8px;
|
355
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
356
|
+
padding: 12px 16px;
|
357
|
+
display: flex;
|
358
|
+
align-items: flex-start;
|
359
|
+
gap: 12px;
|
360
|
+
max-width: 300px;
|
361
|
+
pointer-events: auto;
|
362
|
+
z-index: 1003;
|
363
|
+
border-left: 4px solid #007bff;
|
364
|
+
animation: slideInRight 0.3s ease-out;
|
365
|
+
}
|
366
|
+
|
367
|
+
.window-notification--success { border-left-color: #28a745; }
|
368
|
+
.window-notification--warning { border-left-color: #ffc107; }
|
369
|
+
.window-notification--error { border-left-color: #dc3545; }
|
370
|
+
.window-notification--info { border-left-color: #17a2b8; }
|
371
|
+
|
372
|
+
.window-notification__content {
|
373
|
+
flex: 1;
|
374
|
+
min-width: 0;
|
375
|
+
}
|
376
|
+
|
377
|
+
.window-notification__title {
|
378
|
+
font-weight: 600;
|
379
|
+
font-size: 14px;
|
380
|
+
margin-bottom: 4px;
|
381
|
+
color: #333;
|
382
|
+
}
|
383
|
+
|
384
|
+
.window-notification__message {
|
385
|
+
font-size: 13px;
|
386
|
+
color: #666;
|
387
|
+
line-height: 1.4;
|
388
|
+
}
|
389
|
+
|
390
|
+
.window-notification__close {
|
391
|
+
background: none;
|
392
|
+
border: none;
|
393
|
+
font-size: 16px;
|
394
|
+
cursor: pointer;
|
395
|
+
color: #999;
|
396
|
+
width: 20px;
|
397
|
+
height: 20px;
|
398
|
+
display: flex;
|
399
|
+
align-items: center;
|
400
|
+
justify-content: center;
|
401
|
+
border-radius: 4px;
|
402
|
+
flex-shrink: 0;
|
403
|
+
}
|
404
|
+
|
405
|
+
.window-notification__close:hover {
|
406
|
+
background: rgba(0, 0, 0, 0.1);
|
407
|
+
color: #666;
|
408
|
+
}
|
409
|
+
|
410
|
+
/* Tooltip Styles */
|
411
|
+
.window-tooltip {
|
412
|
+
position: absolute;
|
413
|
+
background: rgba(0, 0, 0, 0.8);
|
414
|
+
color: white;
|
415
|
+
border-radius: 6px;
|
416
|
+
padding: 8px 12px;
|
417
|
+
font-size: 12px;
|
418
|
+
max-width: 200px;
|
419
|
+
pointer-events: auto;
|
420
|
+
z-index: 1005;
|
421
|
+
animation: fadeIn 0.15s ease-out;
|
422
|
+
}
|
423
|
+
|
424
|
+
/* Context Menu Styles */
|
425
|
+
.window-context-menu {
|
426
|
+
position: absolute;
|
427
|
+
background: white;
|
428
|
+
border-radius: 6px;
|
429
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
430
|
+
border: 1px solid #e0e0e0;
|
431
|
+
padding: 4px 0;
|
432
|
+
min-width: 150px;
|
433
|
+
pointer-events: auto;
|
434
|
+
z-index: 1004;
|
435
|
+
animation: fadeIn 0.1s ease-out;
|
436
|
+
}
|
437
|
+
|
438
|
+
.window-context-menu__item {
|
439
|
+
padding: 8px 12px;
|
440
|
+
display: flex;
|
441
|
+
align-items: center;
|
442
|
+
gap: 8px;
|
443
|
+
cursor: pointer;
|
444
|
+
font-size: 13px;
|
445
|
+
color: #333;
|
446
|
+
transition: background-color 0.15s ease;
|
447
|
+
}
|
448
|
+
|
449
|
+
.window-context-menu__item:hover {
|
450
|
+
background: #f8f9fa;
|
451
|
+
}
|
452
|
+
|
453
|
+
/* Overlay Styles */
|
454
|
+
.window-overlay {
|
455
|
+
position: absolute;
|
456
|
+
top: 0;
|
457
|
+
left: 0;
|
458
|
+
right: 0;
|
459
|
+
bottom: 0;
|
460
|
+
background: rgba(0, 0, 0, 0.5);
|
461
|
+
display: flex;
|
462
|
+
align-items: center;
|
463
|
+
justify-content: center;
|
464
|
+
pointer-events: auto;
|
465
|
+
z-index: 1001;
|
466
|
+
animation: fadeIn 0.2s ease-out;
|
467
|
+
}
|
468
|
+
|
469
|
+
.window-overlay__content {
|
470
|
+
background: white;
|
471
|
+
border-radius: 8px;
|
472
|
+
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
|
473
|
+
max-width: 90%;
|
474
|
+
max-height: 90%;
|
475
|
+
overflow: auto;
|
476
|
+
position: relative;
|
477
|
+
}
|
478
|
+
|
479
|
+
/* Animations */
|
480
|
+
@keyframes slideInRight {
|
481
|
+
from { transform: translateX(100%); opacity: 0; }
|
482
|
+
to { transform: translateX(0); opacity: 1; }
|
483
|
+
}
|
484
|
+
|
485
|
+
@keyframes fadeIn {
|
486
|
+
from { opacity: 0; }
|
487
|
+
to { opacity: 1; }
|
488
|
+
}
|
489
|
+
|
259
490
|
/* Window Resize Handles */
|
260
491
|
.window__resize-handle {
|
261
492
|
position: absolute;
|
package/src/desktop/window.js
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
import React, { useState, useRef, useEffect } from 'react'
|
1
|
+
import React, { useState, useRef, useEffect, createContext, useContext, useCallback } from 'react'
|
2
2
|
import { useWindows } from './WindowContext'
|
3
3
|
import './window.css'
|
4
4
|
|
@@ -315,13 +315,14 @@ export const Window = ({
|
|
315
315
|
].filter(Boolean).join(' ')
|
316
316
|
|
317
317
|
return (
|
318
|
-
<
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
318
|
+
<WindowInstanceProvider windowId={id}>
|
319
|
+
<div
|
320
|
+
ref={windowRef}
|
321
|
+
className={windowClasses}
|
322
|
+
style={windowStyle}
|
323
|
+
onClick={handleFocus}
|
324
|
+
{...props}
|
325
|
+
>
|
325
326
|
{/* Window Header */}
|
326
327
|
<div
|
327
328
|
ref={headerRef}
|
@@ -427,6 +428,338 @@ export const Window = ({
|
|
427
428
|
/>
|
428
429
|
</>
|
429
430
|
)}
|
431
|
+
|
432
|
+
{/* Floating Elements Container */}
|
433
|
+
<FloatingElementsRenderer />
|
434
|
+
</div>
|
435
|
+
</WindowInstanceProvider>
|
436
|
+
)
|
437
|
+
}
|
438
|
+
|
439
|
+
/**
|
440
|
+
* Window Instance Context - for managing floating elements within a window
|
441
|
+
*/
|
442
|
+
const WindowInstanceContext = createContext(null)
|
443
|
+
|
444
|
+
/**
|
445
|
+
* WindowInstanceProvider - provides context for floating elements within a window
|
446
|
+
*/
|
447
|
+
export const WindowInstanceProvider = ({ windowId, children }) => {
|
448
|
+
const [dialogs, setDialogs] = useState([])
|
449
|
+
const [notifications, setNotifications] = useState([])
|
450
|
+
const [tooltips, setTooltips] = useState([])
|
451
|
+
const [contextMenus, setContextMenus] = useState([])
|
452
|
+
const [overlays, setOverlays] = useState([])
|
453
|
+
|
454
|
+
const idCounterRef = useRef(0)
|
455
|
+
|
456
|
+
const generateId = useCallback(() => {
|
457
|
+
return `${windowId}-element-${++idCounterRef.current}`
|
458
|
+
}, [windowId])
|
459
|
+
|
460
|
+
// Dialog methods
|
461
|
+
const showDialog = useCallback((config) => {
|
462
|
+
const id = generateId()
|
463
|
+
const dialog = {
|
464
|
+
id,
|
465
|
+
type: 'dialog',
|
466
|
+
...config,
|
467
|
+
createdAt: Date.now(),
|
468
|
+
onClose: () => hideDialog(id)
|
469
|
+
}
|
470
|
+
setDialogs(prev => [...prev, dialog])
|
471
|
+
return id
|
472
|
+
}, [generateId])
|
473
|
+
|
474
|
+
const hideDialog = useCallback((id) => {
|
475
|
+
setDialogs(prev => prev.filter(dialog => dialog.id !== id))
|
476
|
+
}, [])
|
477
|
+
|
478
|
+
const hideAllDialogs = useCallback(() => {
|
479
|
+
setDialogs([])
|
480
|
+
}, [])
|
481
|
+
|
482
|
+
// Notification methods
|
483
|
+
const showNotification = useCallback((config) => {
|
484
|
+
const id = generateId()
|
485
|
+
const notification = {
|
486
|
+
id,
|
487
|
+
type: 'notification',
|
488
|
+
duration: 5000,
|
489
|
+
...config,
|
490
|
+
createdAt: Date.now(),
|
491
|
+
onClose: () => hideNotification(id)
|
492
|
+
}
|
493
|
+
setNotifications(prev => [...prev, notification])
|
494
|
+
|
495
|
+
if (notification.duration > 0) {
|
496
|
+
setTimeout(() => hideNotification(id), notification.duration)
|
497
|
+
}
|
498
|
+
|
499
|
+
return id
|
500
|
+
}, [generateId])
|
501
|
+
|
502
|
+
const hideNotification = useCallback((id) => {
|
503
|
+
setNotifications(prev => prev.filter(notification => notification.id !== id))
|
504
|
+
}, [])
|
505
|
+
|
506
|
+
const hideAllNotifications = useCallback(() => {
|
507
|
+
setNotifications([])
|
508
|
+
}, [])
|
509
|
+
|
510
|
+
// Tooltip methods
|
511
|
+
const showTooltip = useCallback((config) => {
|
512
|
+
const id = generateId()
|
513
|
+
const tooltip = { id, type: 'tooltip', ...config, createdAt: Date.now() }
|
514
|
+
setTooltips(prev => [...prev, tooltip])
|
515
|
+
return id
|
516
|
+
}, [generateId])
|
517
|
+
|
518
|
+
const hideTooltip = useCallback((id) => {
|
519
|
+
setTooltips(prev => prev.filter(tooltip => tooltip.id !== id))
|
520
|
+
}, [])
|
521
|
+
|
522
|
+
const hideAllTooltips = useCallback(() => {
|
523
|
+
setTooltips([])
|
524
|
+
}, [])
|
525
|
+
|
526
|
+
// Context menu methods
|
527
|
+
const showContextMenu = useCallback((config) => {
|
528
|
+
setContextMenus([]) // Only one at a time
|
529
|
+
const id = generateId()
|
530
|
+
const contextMenu = {
|
531
|
+
id,
|
532
|
+
type: 'contextMenu',
|
533
|
+
...config,
|
534
|
+
createdAt: Date.now(),
|
535
|
+
onClose: () => hideContextMenu(id)
|
536
|
+
}
|
537
|
+
setContextMenus([contextMenu])
|
538
|
+
return id
|
539
|
+
}, [generateId])
|
540
|
+
|
541
|
+
const hideContextMenu = useCallback((id) => {
|
542
|
+
setContextMenus(prev => prev.filter(menu => menu.id !== id))
|
543
|
+
}, [])
|
544
|
+
|
545
|
+
const hideAllContextMenus = useCallback(() => {
|
546
|
+
setContextMenus([])
|
547
|
+
}, [])
|
548
|
+
|
549
|
+
// Overlay methods
|
550
|
+
const showOverlay = useCallback((config) => {
|
551
|
+
const id = generateId()
|
552
|
+
const overlay = {
|
553
|
+
id,
|
554
|
+
type: 'overlay',
|
555
|
+
...config,
|
556
|
+
createdAt: Date.now(),
|
557
|
+
onClose: () => hideOverlay(id)
|
558
|
+
}
|
559
|
+
setOverlays(prev => [...prev, overlay])
|
560
|
+
return id
|
561
|
+
}, [generateId])
|
562
|
+
|
563
|
+
const hideOverlay = useCallback((id) => {
|
564
|
+
setOverlays(prev => prev.filter(overlay => overlay.id !== id))
|
565
|
+
}, [])
|
566
|
+
|
567
|
+
const hideAllOverlays = useCallback(() => {
|
568
|
+
setOverlays([])
|
569
|
+
}, [])
|
570
|
+
|
571
|
+
const clearAll = useCallback(() => {
|
572
|
+
setDialogs([])
|
573
|
+
setNotifications([])
|
574
|
+
setTooltips([])
|
575
|
+
setContextMenus([])
|
576
|
+
setOverlays([])
|
577
|
+
}, [])
|
578
|
+
|
579
|
+
const value = {
|
580
|
+
windowId,
|
581
|
+
dialogs, notifications, tooltips, contextMenus, overlays,
|
582
|
+
showDialog, hideDialog, hideAllDialogs,
|
583
|
+
showNotification, hideNotification, hideAllNotifications,
|
584
|
+
showTooltip, hideTooltip, hideAllTooltips,
|
585
|
+
showContextMenu, hideContextMenu, hideAllContextMenus,
|
586
|
+
showOverlay, hideOverlay, hideAllOverlays,
|
587
|
+
clearAll, generateId
|
588
|
+
}
|
589
|
+
|
590
|
+
return (
|
591
|
+
<WindowInstanceContext.Provider value={value}>
|
592
|
+
{children}
|
593
|
+
</WindowInstanceContext.Provider>
|
594
|
+
)
|
595
|
+
}
|
596
|
+
|
597
|
+
/**
|
598
|
+
* Hook to use the window instance context
|
599
|
+
*/
|
600
|
+
export const useWindowInstance = () => {
|
601
|
+
const context = useContext(WindowInstanceContext)
|
602
|
+
if (!context) {
|
603
|
+
throw new Error('useWindowInstance must be used within a WindowInstanceProvider')
|
604
|
+
}
|
605
|
+
return context
|
606
|
+
}
|
607
|
+
|
608
|
+
/**
|
609
|
+
* Specialized hooks for different types of floating elements
|
610
|
+
*/
|
611
|
+
export const useWindowDialogs = () => {
|
612
|
+
const { dialogs, showDialog, hideDialog, hideAllDialogs } = useWindowInstance()
|
613
|
+
return { dialogs, showDialog, hideDialog, hideAllDialogs }
|
614
|
+
}
|
615
|
+
|
616
|
+
export const useWindowNotifications = () => {
|
617
|
+
const { notifications, showNotification, hideNotification, hideAllNotifications } = useWindowInstance()
|
618
|
+
return { notifications, showNotification, hideNotification, hideAllNotifications }
|
619
|
+
}
|
620
|
+
|
621
|
+
export const useWindowTooltips = () => {
|
622
|
+
const { tooltips, showTooltip, hideTooltip, hideAllTooltips } = useWindowInstance()
|
623
|
+
return { tooltips, showTooltip, hideTooltip, hideAllTooltips }
|
624
|
+
}
|
625
|
+
|
626
|
+
export const useWindowContextMenus = () => {
|
627
|
+
const { contextMenus, showContextMenu, hideContextMenu, hideAllContextMenus } = useWindowInstance()
|
628
|
+
return { contextMenus, showContextMenu, hideContextMenu, hideAllContextMenus }
|
629
|
+
}
|
630
|
+
|
631
|
+
export const useWindowOverlays = () => {
|
632
|
+
const { overlays, showOverlay, hideOverlay, hideAllOverlays } = useWindowInstance()
|
633
|
+
return { overlays, showOverlay, hideOverlay, hideAllOverlays }
|
634
|
+
}
|
635
|
+
|
636
|
+
/**
|
637
|
+
* FloatingElementsRenderer - Renders all floating elements for a window
|
638
|
+
*/
|
639
|
+
const FloatingElementsRenderer = () => {
|
640
|
+
const { dialogs, notifications, tooltips, contextMenus, overlays } = useWindowInstance()
|
641
|
+
|
642
|
+
return (
|
643
|
+
<div className="window-floating-elements">
|
644
|
+
{/* Render Overlays */}
|
645
|
+
{overlays.map(overlay => (
|
646
|
+
<div key={overlay.id} className="window-overlay">
|
647
|
+
<div className="window-overlay__content">
|
648
|
+
{overlay.content}
|
649
|
+
{overlay.showCloseButton !== false && (
|
650
|
+
<button
|
651
|
+
className="window-dialog__close"
|
652
|
+
onClick={() => overlay.onClose && overlay.onClose()}
|
653
|
+
style={{ position: 'absolute', top: '8px', right: '8px' }}
|
654
|
+
>
|
655
|
+
×
|
656
|
+
</button>
|
657
|
+
)}
|
658
|
+
</div>
|
659
|
+
</div>
|
660
|
+
))}
|
661
|
+
|
662
|
+
{/* Render Dialogs */}
|
663
|
+
{dialogs.map(dialog => (
|
664
|
+
<div key={dialog.id} className="window-dialog">
|
665
|
+
<div className="window-dialog__content">
|
666
|
+
{dialog.title && (
|
667
|
+
<div className="window-dialog__header">
|
668
|
+
<h3 className="window-dialog__title">{dialog.title}</h3>
|
669
|
+
<button
|
670
|
+
className="window-dialog__close"
|
671
|
+
onClick={() => dialog.onClose && dialog.onClose()}
|
672
|
+
>
|
673
|
+
×
|
674
|
+
</button>
|
675
|
+
</div>
|
676
|
+
)}
|
677
|
+
<div className="window-dialog__body">
|
678
|
+
{dialog.content}
|
679
|
+
</div>
|
680
|
+
{dialog.actions && (
|
681
|
+
<div className="window-dialog__footer">
|
682
|
+
{dialog.actions}
|
683
|
+
</div>
|
684
|
+
)}
|
685
|
+
</div>
|
686
|
+
</div>
|
687
|
+
))}
|
688
|
+
|
689
|
+
{/* Render Notifications */}
|
690
|
+
{notifications.map(notification => (
|
691
|
+
<div
|
692
|
+
key={notification.id}
|
693
|
+
className={`window-notification window-notification--${notification.type || 'info'}`}
|
694
|
+
style={{
|
695
|
+
top: `${16 + notifications.indexOf(notification) * 80}px`
|
696
|
+
}}
|
697
|
+
>
|
698
|
+
{notification.icon && (
|
699
|
+
<div style={{ fontSize: '18px', flexShrink: 0 }}>
|
700
|
+
{notification.icon}
|
701
|
+
</div>
|
702
|
+
)}
|
703
|
+
<div className="window-notification__content">
|
704
|
+
{notification.title && (
|
705
|
+
<div className="window-notification__title">{notification.title}</div>
|
706
|
+
)}
|
707
|
+
<div className="window-notification__message">{notification.message}</div>
|
708
|
+
</div>
|
709
|
+
<button
|
710
|
+
className="window-notification__close"
|
711
|
+
onClick={() => notification.onClose && notification.onClose()}
|
712
|
+
>
|
713
|
+
×
|
714
|
+
</button>
|
715
|
+
</div>
|
716
|
+
))}
|
717
|
+
|
718
|
+
{/* Render Tooltips */}
|
719
|
+
{tooltips.map(tooltip => (
|
720
|
+
<div
|
721
|
+
key={tooltip.id}
|
722
|
+
className="window-tooltip"
|
723
|
+
style={{
|
724
|
+
left: tooltip.x,
|
725
|
+
top: tooltip.y,
|
726
|
+
transform: tooltip.position === 'top' ? 'translateX(-50%) translateY(-100%)' : 'translateX(-50%)'
|
727
|
+
}}
|
728
|
+
>
|
729
|
+
{tooltip.text}
|
730
|
+
</div>
|
731
|
+
))}
|
732
|
+
|
733
|
+
{/* Render Context Menus */}
|
734
|
+
{contextMenus.map(menu => (
|
735
|
+
<div
|
736
|
+
key={menu.id}
|
737
|
+
className="window-context-menu"
|
738
|
+
style={{
|
739
|
+
left: menu.x,
|
740
|
+
top: menu.y
|
741
|
+
}}
|
742
|
+
>
|
743
|
+
{menu.items && menu.items.map((item, index) => (
|
744
|
+
<div
|
745
|
+
key={index}
|
746
|
+
className="window-context-menu__item"
|
747
|
+
onClick={() => {
|
748
|
+
item.onClick && item.onClick()
|
749
|
+
menu.onClose && menu.onClose()
|
750
|
+
}}
|
751
|
+
>
|
752
|
+
{item.icon && <span>{item.icon}</span>}
|
753
|
+
<span>{item.label}</span>
|
754
|
+
{item.shortcut && (
|
755
|
+
<span style={{ marginLeft: 'auto', fontSize: '11px', color: '#999' }}>
|
756
|
+
{item.shortcut}
|
757
|
+
</span>
|
758
|
+
)}
|
759
|
+
</div>
|
760
|
+
))}
|
761
|
+
</div>
|
762
|
+
))}
|
430
763
|
</div>
|
431
764
|
)
|
432
765
|
}
|