@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.
- package/README.md +536 -0
- package/dist/components/ChangePasswordModal.d.ts.map +1 -1
- package/dist/components/ChangePasswordModal.js +22 -11
- package/dist/components/EnterpriseLayout.d.ts.map +1 -1
- package/dist/components/EnterpriseLayout.js +21 -6
- package/dist/components/ui/ActionMenu/ActionMenu.d.ts +52 -0
- package/dist/components/ui/ActionMenu/ActionMenu.d.ts.map +1 -0
- package/dist/components/ui/ActionMenu/ActionMenu.js +116 -0
- package/dist/components/ui/ActionMenu/index.d.ts +3 -0
- package/dist/components/ui/ActionMenu/index.d.ts.map +1 -0
- package/dist/components/ui/ActionMenu/index.js +2 -0
- package/dist/components/ui/ModuleHeader/ModuleHeader.d.ts +90 -0
- package/dist/components/ui/ModuleHeader/ModuleHeader.d.ts.map +1 -0
- package/dist/components/ui/ModuleHeader/ModuleHeader.js +433 -0
- package/dist/components/ui/ModuleHeader/index.d.ts +3 -0
- package/dist/components/ui/ModuleHeader/index.d.ts.map +1 -0
- package/dist/components/ui/ModuleHeader/index.js +1 -0
- package/dist/components/ui/SyutGrid/SyutGrid.d.ts +74 -0
- package/dist/components/ui/SyutGrid/SyutGrid.d.ts.map +1 -0
- package/dist/components/ui/SyutGrid/SyutGrid.js +306 -0
- package/dist/components/ui/SyutGrid/index.d.ts +3 -0
- package/dist/components/ui/SyutGrid/index.d.ts.map +1 -0
- package/dist/components/ui/SyutGrid/index.js +2 -0
- package/dist/components/ui/SyutSelect/SyutSelectUnified.d.ts +128 -0
- package/dist/components/ui/SyutSelect/SyutSelectUnified.d.ts.map +1 -0
- package/dist/components/ui/SyutSelect/SyutSelectUnified.js +679 -0
- package/dist/components/ui/SyutSelect/index.d.ts +3 -0
- package/dist/components/ui/SyutSelect/index.d.ts.map +1 -0
- package/dist/components/ui/SyutSelect/index.js +2 -0
- package/dist/icon-collection/icon-systems.d.ts +89 -0
- package/dist/icon-collection/icon-systems.d.ts.map +1 -0
- package/dist/icon-collection/icon-systems.js +70 -0
- package/dist/icon-collection/index.d.ts +4 -0
- package/dist/icon-collection/index.d.ts.map +1 -0
- package/dist/icon-collection/index.js +8 -0
- package/dist/index.d.ts +12 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +19 -1
- package/package.json +9 -4
- package/src/components/ChangePasswordModal.tsx +26 -14
- package/src/components/EnterpriseLayout.tsx +23 -8
- package/src/components/ui/ActionMenu/ActionMenu.tsx +222 -0
- package/src/components/ui/ActionMenu/index.ts +2 -0
- package/src/components/ui/ModuleHeader/ModuleHeader.tsx +722 -0
- package/src/components/ui/ModuleHeader/index.ts +9 -0
- package/src/components/ui/SyutGrid/SyutGrid.tsx +483 -0
- package/src/components/ui/SyutGrid/index.ts +2 -0
- package/src/components/ui/SyutSelect/SyutSelectUnified.tsx +1115 -0
- package/src/components/ui/SyutSelect/index.ts +3 -0
- package/src/icon-collection/icon-systems.tsx +464 -0
- package/src/icon-collection/index.ts +13 -0
- package/src/index.ts +47 -1
- 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;
|