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.
@@ -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;
@@ -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
- <div
319
- ref={windowRef}
320
- className={windowClasses}
321
- style={windowStyle}
322
- onClick={handleFocus}
323
- {...props}
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
  }