@syuttechnologies/layout 1.0.2 → 1.0.21

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 (53) hide show
  1. package/README.md +536 -0
  2. package/dist/components/ChangePasswordModal.d.ts.map +1 -1
  3. package/dist/components/ChangePasswordModal.js +22 -11
  4. package/dist/components/EnterpriseLayout.d.ts.map +1 -1
  5. package/dist/components/EnterpriseLayout.js +21 -6
  6. package/dist/components/ui/ActionMenu/ActionMenu.d.ts +52 -0
  7. package/dist/components/ui/ActionMenu/ActionMenu.d.ts.map +1 -0
  8. package/dist/components/ui/ActionMenu/ActionMenu.js +116 -0
  9. package/dist/components/ui/ActionMenu/index.d.ts +3 -0
  10. package/dist/components/ui/ActionMenu/index.d.ts.map +1 -0
  11. package/dist/components/ui/ActionMenu/index.js +2 -0
  12. package/dist/components/ui/ModuleHeader/ModuleHeader.d.ts +90 -0
  13. package/dist/components/ui/ModuleHeader/ModuleHeader.d.ts.map +1 -0
  14. package/dist/components/ui/ModuleHeader/ModuleHeader.js +433 -0
  15. package/dist/components/ui/ModuleHeader/index.d.ts +3 -0
  16. package/dist/components/ui/ModuleHeader/index.d.ts.map +1 -0
  17. package/dist/components/ui/ModuleHeader/index.js +1 -0
  18. package/dist/components/ui/SyutGrid/SyutGrid.d.ts +74 -0
  19. package/dist/components/ui/SyutGrid/SyutGrid.d.ts.map +1 -0
  20. package/dist/components/ui/SyutGrid/SyutGrid.js +306 -0
  21. package/dist/components/ui/SyutGrid/index.d.ts +3 -0
  22. package/dist/components/ui/SyutGrid/index.d.ts.map +1 -0
  23. package/dist/components/ui/SyutGrid/index.js +2 -0
  24. package/dist/components/ui/SyutSelect/SyutSelectUnified.d.ts +128 -0
  25. package/dist/components/ui/SyutSelect/SyutSelectUnified.d.ts.map +1 -0
  26. package/dist/components/ui/SyutSelect/SyutSelectUnified.js +679 -0
  27. package/dist/components/ui/SyutSelect/index.d.ts +3 -0
  28. package/dist/components/ui/SyutSelect/index.d.ts.map +1 -0
  29. package/dist/components/ui/SyutSelect/index.js +2 -0
  30. package/dist/icon-collection/icon-systems.d.ts +89 -0
  31. package/dist/icon-collection/icon-systems.d.ts.map +1 -0
  32. package/dist/icon-collection/icon-systems.js +70 -0
  33. package/dist/icon-collection/index.d.ts +4 -0
  34. package/dist/icon-collection/index.d.ts.map +1 -0
  35. package/dist/icon-collection/index.js +8 -0
  36. package/dist/index.d.ts +12 -1
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +19 -1
  39. package/package.json +9 -4
  40. package/src/components/ChangePasswordModal.tsx +26 -14
  41. package/src/components/EnterpriseLayout.tsx +23 -8
  42. package/src/components/ui/ActionMenu/ActionMenu.tsx +222 -0
  43. package/src/components/ui/ActionMenu/index.ts +2 -0
  44. package/src/components/ui/ModuleHeader/ModuleHeader.tsx +722 -0
  45. package/src/components/ui/ModuleHeader/index.ts +9 -0
  46. package/src/components/ui/SyutGrid/SyutGrid.tsx +483 -0
  47. package/src/components/ui/SyutGrid/index.ts +2 -0
  48. package/src/components/ui/SyutSelect/SyutSelectUnified.tsx +1115 -0
  49. package/src/components/ui/SyutSelect/index.ts +3 -0
  50. package/src/icon-collection/icon-systems.tsx +464 -0
  51. package/src/icon-collection/index.ts +13 -0
  52. package/src/index.ts +47 -1
  53. package/tsconfig.tsbuildinfo +1 -1
@@ -0,0 +1,722 @@
1
+ import React, { useState, useRef, useEffect } from 'react';
2
+ import ReactDOM from 'react-dom';
3
+ import IconSystem from '../../../icon-collection/icon-systems';
4
+
5
+ /**
6
+ * ============================================================================
7
+ * MODULE HEADER COMPONENT - REUSABLE HEADER FOR ALL MASTER SCREENS
8
+ * ============================================================================
9
+ *
10
+ * A flexible, configurable header component that can be used across all
11
+ * master screens (Service Master, GL Master, Customer Master, etc.)
12
+ *
13
+ * FEATURES:
14
+ * - Configurable title and description
15
+ * - Optional search functionality
16
+ * - Flexible view mode toggles (list, grid, calendar, etc.)
17
+ * - Optional filter button with active count badge
18
+ * - Custom toolbar items slot (for Column Selector, etc.)
19
+ * - Configurable action buttons (Add, Refresh, Export, Delete, etc.)
20
+ * - Responsive design with mobile overflow menu
21
+ * - Type-safe with TypeScript
22
+ * - Easy to extend without modifying base component
23
+ * ============================================================================
24
+ */
25
+
26
+ // ============================================================================
27
+ // TYPE DEFINITIONS
28
+ // ============================================================================
29
+
30
+ /**
31
+ * Configuration for an individual action button
32
+ */
33
+ export interface ActionButton {
34
+ id: string;
35
+ icon: React.ReactNode;
36
+ label: string;
37
+ onClick: () => void;
38
+ tooltip?: string;
39
+ disabled?: boolean;
40
+ variant?: 'primary' | 'secondary' | 'icon' | 'danger';
41
+ visible?: boolean;
42
+ }
43
+
44
+ /**
45
+ * Configuration for view mode options (list, grid, calendar, etc.)
46
+ */
47
+ export interface ViewModeOption {
48
+ id: string;
49
+ icon: React.ReactNode;
50
+ label: string;
51
+ tooltip?: string;
52
+ disabled?: boolean;
53
+ }
54
+
55
+ /**
56
+ * Configuration for the search bar
57
+ */
58
+ export interface SearchConfig {
59
+ value: string;
60
+ onChange: (value: string) => void;
61
+ placeholder?: string;
62
+ disabled?: boolean;
63
+ }
64
+
65
+ /**
66
+ * Configuration for view mode toggle
67
+ */
68
+ export interface ViewModeConfig {
69
+ currentMode: string;
70
+ modes: ViewModeOption[];
71
+ onChange: (mode: string) => void;
72
+ }
73
+
74
+ /**
75
+ * Configuration for the filter button
76
+ */
77
+ export interface FilterConfig {
78
+ onClick: () => void;
79
+ isActive?: boolean;
80
+ activeCount?: number;
81
+ badgeCount?: number; // Alias for activeCount for compatibility
82
+ disabled?: boolean;
83
+ tooltip?: string;
84
+ }
85
+
86
+ /**
87
+ * Main props for the ModuleHeader component
88
+ */
89
+ export interface ModuleHeaderProps {
90
+ title: string;
91
+ description?: string;
92
+ searchConfig?: SearchConfig;
93
+ viewModeConfig?: ViewModeConfig;
94
+ filterConfig?: FilterConfig;
95
+ customToolbarItems?: React.ReactNode;
96
+ actions?: ActionButton[];
97
+ showActionSeparator?: boolean;
98
+ visible?: boolean;
99
+ className?: string;
100
+ }
101
+
102
+ // Inject styles
103
+ if (typeof document !== 'undefined') {
104
+ const styleId = 'module-header-styles';
105
+ if (!document.getElementById(styleId)) {
106
+ const style = document.createElement('style');
107
+ style.id = styleId;
108
+ style.textContent = `
109
+ .module-header {
110
+ position: sticky;
111
+ top: 0;
112
+ display: flex;
113
+ align-items: center;
114
+ justify-content: space-between;
115
+ padding: 0.875rem 1.5rem;
116
+ min-height: 2.75rem;
117
+ background: rgba(255, 255, 255, 0.95);
118
+ backdrop-filter: blur(12px);
119
+ border-radius: 0.75rem;
120
+ margin: 0.625rem 1.25rem;
121
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
122
+ border: 1px solid rgba(226, 232, 240, 0.5);
123
+ gap: 0.875rem;
124
+ flex-wrap: wrap;
125
+ flex-shrink: 0;
126
+ }
127
+
128
+ .module-header-content {
129
+ display: flex;
130
+ flex-direction: column;
131
+ gap: 0.125rem;
132
+ }
133
+
134
+ .module-title {
135
+ font-size: 1.1rem;
136
+ font-weight: 700;
137
+ background: linear-gradient(135deg, #2563eb, #0ea5e9);
138
+ -webkit-background-clip: text;
139
+ -webkit-text-fill-color: transparent;
140
+ background-clip: text;
141
+ margin: 0;
142
+ line-height: 1.2;
143
+ }
144
+
145
+ .module-description {
146
+ font-size: 0.75rem;
147
+ color: #64748b;
148
+ margin: 0;
149
+ }
150
+
151
+ .module-toolbar {
152
+ display: flex;
153
+ align-items: center;
154
+ gap: 0.5rem;
155
+ flex-wrap: wrap;
156
+ }
157
+
158
+ .toolbar-separator {
159
+ width: 1px;
160
+ height: 1.5rem;
161
+ background: rgba(226, 232, 240, 0.8);
162
+ margin: 0 0.25rem;
163
+ }
164
+
165
+ /* Desktop toolbar items - visible on larger screens */
166
+ .toolbar-desktop-items {
167
+ display: flex;
168
+ align-items: center;
169
+ gap: 0.5rem;
170
+ flex-wrap: wrap;
171
+ }
172
+
173
+ /* Mobile overflow menu - hidden on larger screens */
174
+ .toolbar-overflow-menu {
175
+ display: none;
176
+ position: relative;
177
+ }
178
+
179
+ /* Responsive: Show overflow menu on mobile, hide desktop items */
180
+ @media (max-width: 768px) {
181
+ .module-header {
182
+ padding: 0.75rem 1rem;
183
+ margin: 0.5rem 0.75rem;
184
+ gap: 0.5rem;
185
+ }
186
+
187
+ .module-title {
188
+ font-size: 1rem;
189
+ }
190
+
191
+ .module-description {
192
+ font-size: 0.7rem;
193
+ }
194
+
195
+ .search-input-wrapper {
196
+ min-width: 150px;
197
+ flex: 1;
198
+ }
199
+
200
+ .toolbar-desktop-items {
201
+ display: none !important;
202
+ }
203
+
204
+ .toolbar-overflow-menu {
205
+ display: block !important;
206
+ }
207
+ }
208
+
209
+ .search-input-wrapper {
210
+ display: flex;
211
+ align-items: center;
212
+ gap: 0.5rem;
213
+ padding: 0.5rem 0.75rem;
214
+ background: #f8fafc;
215
+ border: 1px solid rgba(226, 232, 240, 0.8);
216
+ border-radius: 0.5rem;
217
+ min-width: 200px;
218
+ transition: all 0.2s ease;
219
+ }
220
+
221
+ .search-input-wrapper:focus-within {
222
+ border-color: #2563eb;
223
+ box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);
224
+ }
225
+
226
+ .search-input-wrapper svg {
227
+ color: #64748b;
228
+ flex-shrink: 0;
229
+ }
230
+
231
+ .search-input {
232
+ flex: 1;
233
+ border: none;
234
+ background: transparent;
235
+ font-size: 0.85rem;
236
+ color: #1e293b;
237
+ outline: none;
238
+ }
239
+
240
+ .search-input::placeholder {
241
+ color: #94a3b8;
242
+ }
243
+
244
+ .view-mode-group {
245
+ display: inline-flex;
246
+ align-items: center;
247
+ background: #ffffff;
248
+ border: 1px solid rgba(226, 232, 240, 0.8);
249
+ border-radius: 0.375rem;
250
+ padding: 0.125rem;
251
+ gap: 0.125rem;
252
+ }
253
+
254
+ .view-mode-btn {
255
+ display: inline-flex;
256
+ align-items: center;
257
+ justify-content: center;
258
+ width: 2rem;
259
+ height: 2rem;
260
+ padding: 0;
261
+ border: none;
262
+ background: transparent;
263
+ border-radius: 0.375rem;
264
+ cursor: pointer;
265
+ transition: all 0.2s ease;
266
+ color: #64748b;
267
+ }
268
+
269
+ .view-mode-btn:hover {
270
+ background: #f8fafc;
271
+ color: #1e293b;
272
+ }
273
+
274
+ .view-mode-btn.active {
275
+ background: #2563eb;
276
+ color: white;
277
+ box-shadow: 0 1px 3px rgba(37, 99, 235, 0.3);
278
+ }
279
+
280
+ .view-mode-btn svg {
281
+ width: 16px;
282
+ height: 16px;
283
+ }
284
+
285
+ .btn-icon {
286
+ display: inline-flex;
287
+ align-items: center;
288
+ justify-content: center;
289
+ width: 2.25rem;
290
+ height: 2.25rem;
291
+ padding: 0;
292
+ border: 1px solid rgba(226, 232, 240, 0.8);
293
+ background: #ffffff;
294
+ border-radius: 0.5rem;
295
+ cursor: pointer;
296
+ transition: all 0.2s ease;
297
+ color: #64748b;
298
+ position: relative;
299
+ }
300
+
301
+ .btn-icon:hover {
302
+ background: #f8fafc;
303
+ border-color: #2563eb;
304
+ color: #2563eb;
305
+ }
306
+
307
+ .btn-icon.active {
308
+ background: linear-gradient(135deg, #2563eb, #0ea5e9);
309
+ color: white;
310
+ border-color: #2563eb;
311
+ }
312
+
313
+ .btn-icon svg {
314
+ width: 18px;
315
+ height: 18px;
316
+ }
317
+
318
+ .btn {
319
+ display: inline-flex;
320
+ align-items: center;
321
+ justify-content: center;
322
+ padding: 0.5rem 1rem;
323
+ border-radius: 0.5rem;
324
+ font-size: 0.85rem;
325
+ font-weight: 600;
326
+ cursor: pointer;
327
+ transition: all 0.2s ease;
328
+ border: none;
329
+ gap: 0.25rem;
330
+ }
331
+
332
+ .btn-primary {
333
+ background: linear-gradient(135deg, #2563eb, #0ea5e9);
334
+ color: white;
335
+ box-shadow: 0 2px 4px rgba(37, 99, 235, 0.3);
336
+ }
337
+
338
+ .btn-primary:hover {
339
+ background: linear-gradient(135deg, #1d4ed8, #0284c7);
340
+ transform: translateY(-1px);
341
+ box-shadow: 0 4px 8px rgba(37, 99, 235, 0.4);
342
+ }
343
+
344
+ .btn-secondary {
345
+ background: #ffffff;
346
+ color: #1e293b;
347
+ border: 1px solid rgba(226, 232, 240, 0.8);
348
+ }
349
+
350
+ .btn-secondary:hover {
351
+ background: #f8fafc;
352
+ border-color: #2563eb;
353
+ color: #2563eb;
354
+ }
355
+
356
+ .btn-danger {
357
+ background: linear-gradient(135deg, #dc2626, #ef4444);
358
+ color: white;
359
+ }
360
+
361
+ .btn-danger:hover {
362
+ background: linear-gradient(135deg, #b91c1c, #dc2626);
363
+ }
364
+
365
+ .filter-badge {
366
+ position: absolute;
367
+ top: -4px;
368
+ right: -4px;
369
+ background: #3B82F6;
370
+ color: white;
371
+ border-radius: 50%;
372
+ width: 16px;
373
+ height: 16px;
374
+ font-size: 10px;
375
+ display: flex;
376
+ align-items: center;
377
+ justify-content: center;
378
+ font-weight: 600;
379
+ }
380
+
381
+ /* Overflow menu trigger */
382
+ .action-menu-trigger {
383
+ display: inline-flex;
384
+ align-items: center;
385
+ justify-content: center;
386
+ width: 2.25rem;
387
+ height: 2.25rem;
388
+ padding: 0;
389
+ border: 1px solid rgba(226, 232, 240, 0.8);
390
+ background: #ffffff;
391
+ border-radius: 0.5rem;
392
+ cursor: pointer;
393
+ transition: all 0.2s ease;
394
+ color: #64748b;
395
+ }
396
+
397
+ .action-menu-trigger:hover,
398
+ .action-menu-trigger.active {
399
+ background: #f8fafc;
400
+ border-color: #2563eb;
401
+ color: #2563eb;
402
+ }
403
+
404
+ /* Overflow dropdown styles */
405
+ .overflow-menu-section-label {
406
+ padding: 0.5rem 0.875rem 0.25rem;
407
+ font-size: 0.7rem;
408
+ font-weight: 600;
409
+ color: #94a3b8;
410
+ text-transform: uppercase;
411
+ letter-spacing: 0.5px;
412
+ }
413
+
414
+ .overflow-menu-item {
415
+ display: flex;
416
+ align-items: center;
417
+ gap: 0.75rem;
418
+ width: 100%;
419
+ padding: 0.625rem 1rem;
420
+ border: none;
421
+ background: transparent;
422
+ color: #334155;
423
+ font-size: 0.875rem;
424
+ cursor: pointer;
425
+ text-align: left;
426
+ transition: background 0.15s ease;
427
+ }
428
+
429
+ .overflow-menu-item:hover {
430
+ background: #f1f5f9;
431
+ }
432
+
433
+ .overflow-menu-item.active {
434
+ background: #f1f5f9;
435
+ color: #3b82f6;
436
+ }
437
+
438
+ .overflow-menu-item.danger {
439
+ color: #ef4444;
440
+ }
441
+
442
+ .overflow-menu-item.danger:hover {
443
+ background: rgba(239, 68, 68, 0.1);
444
+ }
445
+
446
+ .overflow-menu-item:disabled {
447
+ opacity: 0.5;
448
+ cursor: not-allowed;
449
+ }
450
+ `;
451
+ document.head.appendChild(style);
452
+ }
453
+ }
454
+
455
+ // ============================================================================
456
+ // MODULE HEADER COMPONENT
457
+ // ============================================================================
458
+
459
+ const ModuleHeader: React.FC<ModuleHeaderProps> = ({
460
+ title,
461
+ description,
462
+ searchConfig,
463
+ viewModeConfig,
464
+ filterConfig,
465
+ customToolbarItems,
466
+ actions = [],
467
+ showActionSeparator = true,
468
+ visible = true,
469
+ className = ''
470
+ }) => {
471
+ const [overflowMenuOpen, setOverflowMenuOpen] = useState(false);
472
+ const overflowMenuRef = useRef<HTMLDivElement>(null);
473
+
474
+ // Close overflow menu when clicking outside
475
+ useEffect(() => {
476
+ const handleClickOutside = (event: MouseEvent) => {
477
+ const target = event.target as Node;
478
+ const dropdownEl = document.querySelector('.module-header-overflow-dropdown');
479
+
480
+ if (
481
+ overflowMenuRef.current &&
482
+ !overflowMenuRef.current.contains(target) &&
483
+ (!dropdownEl || !dropdownEl.contains(target))
484
+ ) {
485
+ setOverflowMenuOpen(false);
486
+ }
487
+ };
488
+
489
+ if (overflowMenuOpen) {
490
+ document.addEventListener('mousedown', handleClickOutside);
491
+ }
492
+
493
+ return () => {
494
+ document.removeEventListener('mousedown', handleClickOutside);
495
+ };
496
+ }, [overflowMenuOpen]);
497
+
498
+ if (!visible) {
499
+ return null;
500
+ }
501
+
502
+ const visibleActions = actions.filter(action => action.visible !== false);
503
+
504
+ // Get badge count from either activeCount or badgeCount
505
+ const filterBadgeCount = filterConfig?.activeCount || filterConfig?.badgeCount || 0;
506
+
507
+ return (
508
+ <div className={`module-header ${className}`}>
509
+ {/* Title Section */}
510
+ <div className="module-header-content">
511
+ <h1 className="module-title">{title}</h1>
512
+ {description && <p className="module-description">{description}</p>}
513
+ </div>
514
+
515
+ {/* Toolbar Section */}
516
+ <div className="module-toolbar">
517
+ {/* Search Bar - Always visible */}
518
+ {searchConfig && (
519
+ <div className="search-input-wrapper">
520
+ <IconSystem.search size={16} />
521
+ <input
522
+ type="text"
523
+ className="search-input"
524
+ placeholder={searchConfig.placeholder || 'Search...'}
525
+ value={searchConfig.value}
526
+ onChange={(e) => searchConfig.onChange(e.target.value)}
527
+ disabled={searchConfig.disabled}
528
+ />
529
+ </div>
530
+ )}
531
+
532
+ {/* Desktop Toolbar Items - Hidden on mobile */}
533
+ <div className="toolbar-desktop-items">
534
+ {/* View Mode Toggle */}
535
+ {viewModeConfig && (
536
+ <div className="view-mode-group">
537
+ {viewModeConfig.modes.map((mode) => (
538
+ <button
539
+ key={mode.id}
540
+ className={`view-mode-btn ${viewModeConfig.currentMode === mode.id ? 'active' : ''}`}
541
+ onClick={() => viewModeConfig.onChange(mode.id)}
542
+ disabled={mode.disabled}
543
+ title={mode.tooltip || mode.label}
544
+ style={mode.disabled ? { opacity: 0.3, cursor: 'not-allowed' } : {}}
545
+ >
546
+ {mode.icon}
547
+ </button>
548
+ ))}
549
+ </div>
550
+ )}
551
+
552
+ {/* Filter Button */}
553
+ {filterConfig && (
554
+ <button
555
+ className={`btn-icon ${filterConfig.isActive ? 'active' : ''}`}
556
+ onClick={filterConfig.onClick}
557
+ disabled={filterConfig.disabled}
558
+ title={filterConfig.tooltip || 'Filters'}
559
+ style={{ position: 'relative' }}
560
+ >
561
+ <IconSystem.filter size={18} />
562
+ {filterBadgeCount > 0 && (
563
+ <span className="filter-badge">{filterBadgeCount}</span>
564
+ )}
565
+ </button>
566
+ )}
567
+
568
+ {/* Custom Toolbar Items */}
569
+ {customToolbarItems}
570
+
571
+ {/* Separator before actions */}
572
+ {showActionSeparator && visibleActions.length > 0 && (
573
+ <div className="toolbar-separator" />
574
+ )}
575
+
576
+ {/* Action Buttons */}
577
+ {visibleActions.map((action) => {
578
+ const buttonClass = action.variant === 'primary'
579
+ ? 'btn btn-primary'
580
+ : action.variant === 'secondary'
581
+ ? 'btn btn-secondary'
582
+ : action.variant === 'danger'
583
+ ? 'btn btn-danger'
584
+ : 'btn-icon';
585
+
586
+ return (
587
+ <button
588
+ key={action.id}
589
+ className={buttonClass}
590
+ onClick={action.onClick}
591
+ disabled={action.disabled}
592
+ title={action.tooltip || action.label}
593
+ >
594
+ {action.icon}
595
+ {action.variant !== 'icon' && (
596
+ <span style={{ marginLeft: '0.5rem' }}>{action.label}</span>
597
+ )}
598
+ </button>
599
+ );
600
+ })}
601
+ </div>
602
+
603
+ {/* Mobile Overflow Menu - Visible only on mobile */}
604
+ <div
605
+ ref={overflowMenuRef}
606
+ className="toolbar-overflow-menu"
607
+ >
608
+ <button
609
+ onClick={() => setOverflowMenuOpen(!overflowMenuOpen)}
610
+ className={`action-menu-trigger ${overflowMenuOpen ? 'active' : ''}`}
611
+ title="More options"
612
+ >
613
+ <IconSystem.moreVertical size={18} />
614
+ </button>
615
+
616
+ {overflowMenuOpen && ReactDOM.createPortal(
617
+ <div
618
+ className="module-header-overflow-dropdown"
619
+ style={{
620
+ position: 'fixed',
621
+ top: overflowMenuRef.current ? overflowMenuRef.current.getBoundingClientRect().bottom + 4 : 0,
622
+ right: overflowMenuRef.current ? window.innerWidth - overflowMenuRef.current.getBoundingClientRect().right : 0,
623
+ minWidth: '200px',
624
+ maxHeight: overflowMenuRef.current ? `calc(100vh - ${overflowMenuRef.current.getBoundingClientRect().bottom + 20}px)` : '70vh',
625
+ overflowY: 'auto',
626
+ padding: '0.5rem 0',
627
+ zIndex: 999999,
628
+ background: 'white',
629
+ borderRadius: '0.5rem',
630
+ boxShadow: '0 10px 40px rgba(0, 0, 0, 0.2)',
631
+ border: '1px solid #e2e8f0',
632
+ }}
633
+ >
634
+ {/* View Mode Options */}
635
+ {viewModeConfig && (
636
+ <>
637
+ <div className="overflow-menu-section-label">View</div>
638
+ {viewModeConfig.modes.map((mode) => (
639
+ <button
640
+ key={mode.id}
641
+ className={`overflow-menu-item ${viewModeConfig.currentMode === mode.id ? 'active' : ''}`}
642
+ onClick={() => {
643
+ viewModeConfig.onChange(mode.id);
644
+ setOverflowMenuOpen(false);
645
+ }}
646
+ disabled={mode.disabled}
647
+ >
648
+ {mode.icon}
649
+ <span>{mode.label}</span>
650
+ </button>
651
+ ))}
652
+ </>
653
+ )}
654
+
655
+ {/* Filter Option */}
656
+ {filterConfig && (
657
+ <button
658
+ className={`overflow-menu-item ${filterConfig.isActive ? 'active' : ''}`}
659
+ onClick={() => {
660
+ filterConfig.onClick();
661
+ setOverflowMenuOpen(false);
662
+ }}
663
+ disabled={filterConfig.disabled}
664
+ >
665
+ <IconSystem.filter size={18} />
666
+ <span>Filters</span>
667
+ {filterBadgeCount > 0 && (
668
+ <span style={{
669
+ marginLeft: 'auto',
670
+ backgroundColor: '#3B82F6',
671
+ color: 'white',
672
+ fontSize: '10px',
673
+ fontWeight: 600,
674
+ minWidth: '16px',
675
+ height: '16px',
676
+ borderRadius: '8px',
677
+ display: 'flex',
678
+ alignItems: 'center',
679
+ justifyContent: 'center',
680
+ padding: '0 4px',
681
+ }}>
682
+ {filterBadgeCount}
683
+ </span>
684
+ )}
685
+ </button>
686
+ )}
687
+
688
+ {/* Separator if we have actions */}
689
+ {visibleActions.length > 0 && (viewModeConfig || filterConfig) && (
690
+ <div style={{ height: '1px', background: '#e2e8f0', margin: '0.5rem 0' }} />
691
+ )}
692
+
693
+ {/* Action Buttons */}
694
+ {visibleActions.length > 0 && (
695
+ <>
696
+ <div className="overflow-menu-section-label">Actions</div>
697
+ {visibleActions.map((action) => (
698
+ <button
699
+ key={action.id}
700
+ className={`overflow-menu-item ${action.variant === 'danger' ? 'danger' : ''}`}
701
+ onClick={() => {
702
+ action.onClick();
703
+ setOverflowMenuOpen(false);
704
+ }}
705
+ disabled={action.disabled}
706
+ >
707
+ {action.icon}
708
+ <span>{action.label}</span>
709
+ </button>
710
+ ))}
711
+ </>
712
+ )}
713
+ </div>,
714
+ document.body
715
+ )}
716
+ </div>
717
+ </div>
718
+ </div>
719
+ );
720
+ };
721
+
722
+ export default ModuleHeader;