@topconsultnpm/sdkui-react-beta 6.13.33 → 6.13.35

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.
@@ -0,0 +1,9 @@
1
+ import { TMPanelItemConfig } from './TMPanelManagerUtils';
2
+ type TMPanelManagerProps = {
3
+ panels: Array<TMPanelItemConfig>;
4
+ initialMobilePanelId: string;
5
+ showToolbar?: boolean;
6
+ gutters?: number;
7
+ };
8
+ declare const TMPanelManager: (props: TMPanelManagerProps) => import("react/jsx-runtime").JSX.Element;
9
+ export default TMPanelManager;
@@ -0,0 +1,465 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useMemo, useRef, useState } from 'react';
3
+ import { flattenPanels, isAtLeastOnePanelVisible, isPanelArray } from './TMPanelManagerUtils';
4
+ import { SDKUI_Globals, IconInfo, SDKUI_Localizator } from '../../helper';
5
+ import { useDeviceType, DeviceType } from './TMDeviceProvider';
6
+ import TMPanel from './TMPanel';
7
+ import TMPanelManagerToolbar from './TMPanelManagerToolbar';
8
+ const TMPanelManager = (props) => {
9
+ const { panels, initialMobilePanelId, showToolbar = true, gutters = SDKUI_Globals.userSettings.themeSettings.gutters } = props;
10
+ const deviceType = useDeviceType();
11
+ let isMobile = useMemo(() => { return deviceType === DeviceType.MOBILE; }, [deviceType]);
12
+ const allPanels = useMemo(() => flattenPanels(panels), [panels]);
13
+ const panelMap = useMemo(() => {
14
+ const map = new Map();
15
+ allPanels.forEach(p => map.set(p.fullId, p));
16
+ return map;
17
+ }, [allPanels]);
18
+ const panelRefs = useRef({});
19
+ // Active buttons state & panel map
20
+ const [activeButtons, setActiveButtons] = useState(() => Object.fromEntries(allPanels.map(p => [p.fullId, p.config.toolbarOptions?.isActive ?? false])));
21
+ const [originalSizes, setOriginalSizes] = useState(new Map());
22
+ const [panelsState, setPanelsState] = useState(new Map());
23
+ const [maximizedPanelPath, setMaximizedPanelPath] = useState([]);
24
+ const [isRowDragging, setIsRowDragging] = useState(null);
25
+ const [isColumnDragging, setIsColumnDragging] = useState(null);
26
+ const [updatedDisabledButton, setUpdatedDisabledButton] = useState(undefined);
27
+ const [updatedVisibleButton, setUpdatedVisibleButton] = useState(undefined);
28
+ // Initialize two new Maps to store panel state and their original sizes
29
+ useEffect(() => {
30
+ const newState = new Map();
31
+ const newOriginalSizes = new Map();
32
+ // Recursive function to initialize panel map data
33
+ const initPanelsMap = (panels, parentPath = '') => {
34
+ panels.forEach(panel => {
35
+ // Skip if the panel has no contentOptions
36
+ if (!panel.contentOptions)
37
+ return;
38
+ // Construct a unique full ID for each panel based on its parent path
39
+ const fullId = parentPath ? `${parentPath}.${panel.id}` : panel.id;
40
+ // Use default values '0' if width/height is not provided
41
+ const width = panel.contentOptions.width;
42
+ const height = panel.contentOptions.height;
43
+ // Check if the panel contains nested children
44
+ const hasChildren = Array.isArray(panel.contentOptions.content);
45
+ // Store the original width and height for future reference
46
+ newOriginalSizes.set(fullId, { width, height });
47
+ // Determine the layout orientation
48
+ // A panel is considered a "column" layout if height is 100% and width isn't.
49
+ // A panel is considered a "row" layout if width is 100% and height isn't.
50
+ const isColumn = height === '100%' && width !== '100%';
51
+ const isRow = width === '100%' && height !== '100%';
52
+ // Save the current panel's state including dimensions and layout direction
53
+ newState.set(fullId, { width, height, isColumn, isRow, });
54
+ // If the panel has nested children, recursively initialize their states
55
+ if (hasChildren) {
56
+ initPanelsMap(panel.contentOptions.content, fullId);
57
+ }
58
+ });
59
+ };
60
+ initPanelsMap(panels);
61
+ setOriginalSizes(newOriginalSizes);
62
+ setPanelsState(newState);
63
+ }, [panels]);
64
+ useEffect(() => {
65
+ // If there are no original sizes available, exit early
66
+ if (!originalSizes.size)
67
+ return;
68
+ // Clone the current state of the panels into a new Map to modify it safely
69
+ const newState = new Map(panelsState);
70
+ // Helper function to check if a panel is currently maximized
71
+ const isMaximized = (panelId) => maximizedPanelPath.includes(panelId);
72
+ // Iterate through all panel sizes stored in originalSizes
73
+ for (const [panelId, sizes] of originalSizes.entries()) {
74
+ const currentPanel = newState.get(panelId);
75
+ if (!currentPanel) {
76
+ // If for some reason the panel doesn't exist in state, skip it
77
+ continue;
78
+ }
79
+ if (isMaximized(panelId)) {
80
+ // If the panel is maximized, set its dimensions to take up the full available space
81
+ newState.set(panelId, { ...currentPanel, width: '100%', height: '100%' });
82
+ }
83
+ else if (maximizedPanelPath.length > 0) {
84
+ // If at least one panel is maximized, collapse all others to zero size
85
+ newState.set(panelId, { ...currentPanel, width: '0', height: '0' });
86
+ }
87
+ else {
88
+ // If no panels are maximized, restore their original dimensions
89
+ newState.set(panelId, { ...currentPanel, width: sizes.width, height: sizes.height });
90
+ }
91
+ }
92
+ // Apply the updated panel states
93
+ setPanelsState(newState);
94
+ // Run this effect whenever maximizedPanelPath or originalSizes change
95
+ }, [maximizedPanelPath, originalSizes]);
96
+ useEffect(() => {
97
+ const isDragging = isColumnDragging || isRowDragging;
98
+ // Handles the end of dragging (mouse release)
99
+ const onMouseUp = () => {
100
+ setIsColumnDragging(null);
101
+ setIsRowDragging(null);
102
+ document.body.style.userSelect = '';
103
+ };
104
+ // Handles column resizing during dragging
105
+ const onColumnMouseMove = (e) => {
106
+ if (!isColumnDragging)
107
+ return;
108
+ const columnKeys = getKeysByOrientation('column');
109
+ const currentIndex = columnKeys.indexOf(isColumnDragging);
110
+ const nextKey = columnKeys[currentIndex + 1];
111
+ if (!nextKey)
112
+ return;
113
+ const deltaPercent = (e.movementX / window.innerWidth) * 100;
114
+ resizePanels(columnKeys, currentIndex, nextKey, deltaPercent, 'width');
115
+ };
116
+ // Handles row resizing during dragging
117
+ const onRowMouseMove = (e) => {
118
+ if (!isRowDragging)
119
+ return;
120
+ const rowKeys = getKeysByOrientation('row');
121
+ const currentIndex = rowKeys.indexOf(isRowDragging);
122
+ const nextKey = rowKeys[currentIndex + 1];
123
+ if (!nextKey)
124
+ return;
125
+ const deltaPercent = (e.movementY / window.innerHeight) * 100;
126
+ resizePanels(rowKeys, currentIndex, nextKey, deltaPercent, 'height');
127
+ };
128
+ // Returns panel keys that match the given orientation (row or column)
129
+ const getKeysByOrientation = (type) => {
130
+ return Array.from(panelsState.entries())
131
+ .filter(([_, value]) => (type === 'row' ? value.isRow : value.isColumn))
132
+ .map(([key]) => key);
133
+ };
134
+ // Shared resizing logic for both rows and columns
135
+ const resizePanels = (keys, currentIndex, nextKey, deltaPercent, dimension) => {
136
+ setPanelsState(prev => {
137
+ const updated = new Map(prev);
138
+ const updatedOriginalSizes = new Map(originalSizes);
139
+ const currentKey = keys[currentIndex];
140
+ const currentSize = parseFloat(updated.get(currentKey)?.[dimension] || '0');
141
+ const nextSize = parseFloat(updated.get(nextKey)?.[dimension] || '0');
142
+ const total = currentSize + nextSize;
143
+ let newCurrent = Math.min(currentSize + deltaPercent, 100);
144
+ let newNext = Math.min(nextSize - deltaPercent, 100);
145
+ // Enforce minimum panel size
146
+ if (newCurrent < 5) {
147
+ newCurrent = 5;
148
+ newNext = total - newCurrent;
149
+ }
150
+ else if (newNext < 5) {
151
+ newNext = 5;
152
+ newCurrent = total - newNext;
153
+ }
154
+ // Safely get current and next panel data
155
+ const currentPanel = updated.get(currentKey);
156
+ const nextPanel = updated.get(nextKey);
157
+ // Ensure both panels exist before updating
158
+ if (!currentPanel || !nextPanel)
159
+ return prev;
160
+ // Update panel dimensions
161
+ updated.set(currentKey, { ...currentPanel, [dimension]: `${newCurrent}%` });
162
+ updated.set(nextKey, { ...nextPanel, [dimension]: `${newNext}%` });
163
+ // Update original sizes for persistence
164
+ updatedOriginalSizes.set(currentKey, { ...currentPanel, [dimension]: `${newCurrent}%` });
165
+ updatedOriginalSizes.set(nextKey, { ...nextPanel, [dimension]: `${newNext}%` });
166
+ setOriginalSizes(updatedOriginalSizes);
167
+ return updated;
168
+ });
169
+ };
170
+ // Add event listeners when dragging starts
171
+ if (isDragging) {
172
+ if (isColumnDragging) {
173
+ window.addEventListener('mousemove', onColumnMouseMove);
174
+ }
175
+ else if (isRowDragging) {
176
+ window.addEventListener('mousemove', onRowMouseMove);
177
+ }
178
+ window.addEventListener('mouseup', onMouseUp);
179
+ document.body.style.userSelect = 'none'; // Prevents text selection during drag
180
+ }
181
+ // Cleanup function to remove event listeners
182
+ return () => {
183
+ window.removeEventListener('mousemove', onColumnMouseMove);
184
+ window.removeEventListener('mousemove', onRowMouseMove);
185
+ window.removeEventListener('mouseup', onMouseUp);
186
+ document.body.style.userSelect = '';
187
+ };
188
+ }, [isColumnDragging, isRowDragging, panelsState, originalSizes]);
189
+ useEffect(() => {
190
+ if (isMobile) {
191
+ setActiveButtons(prev => {
192
+ // Clone the previous state to avoid mutating it directly
193
+ const newActive = { ...prev };
194
+ // Get the panel object from the panel map
195
+ const panel = panelMap.get(initialMobilePanelId);
196
+ Object.keys(newActive).forEach(panelId => {
197
+ newActive[panelId] = false;
198
+ });
199
+ // Toggle the selected panel's visibility
200
+ newActive[initialMobilePanelId] = true;
201
+ // If the panel is being hidden and has children, recursively hide its children
202
+ if (panel && panel.childrenIds.length > 0 && !newActive[initialMobilePanelId]) {
203
+ const hideChildrenRecursively = (ids) => {
204
+ ids.forEach(childId => {
205
+ newActive[childId] = false;
206
+ const childPanel = panelMap.get(childId);
207
+ if (childPanel?.childrenIds.length) {
208
+ hideChildrenRecursively(childPanel.childrenIds);
209
+ }
210
+ });
211
+ };
212
+ hideChildrenRecursively(panel.childrenIds);
213
+ }
214
+ // After toggling, ensure the visibility of parent panels is in sync with their children
215
+ if (panel?.parentId) {
216
+ syncParentVisibilityWithChildren(panel.parentId, newActive);
217
+ }
218
+ // Return the updated visibility map
219
+ return newActive;
220
+ });
221
+ }
222
+ }, [isMobile]);
223
+ // Handler for starting to drag a column divider (for resizing panels horizontally)
224
+ const onColumnMouseDown = (panelKey) => {
225
+ setIsColumnDragging(panelKey); // Set the state to indicate which column is being dragged
226
+ };
227
+ // Handler for starting to drag a row divider (for resizing panels vertically)
228
+ const onRowMouseDown = (rowKey) => {
229
+ setIsRowDragging(rowKey); // Set the state to indicate which row is being dragged
230
+ };
231
+ // Function to disable a toolbar button by its ID
232
+ const handleDisableButton = (id, value) => {
233
+ // Update state to inform toolbar which button should be disabled/enabled
234
+ setUpdatedDisabledButton({ id, value });
235
+ };
236
+ // Function to change the visibility of a toolbar button by its ID
237
+ const handleVisibilityButton = (id, value) => {
238
+ // Update state to inform toolbar which button should be shown/hidden
239
+ setUpdatedVisibleButton({ id, value });
240
+ };
241
+ /**
242
+ * Recursively updates the visibility status of parent panels based on the visibility of their child panels.
243
+ * If all children are hidden, the parent is hidden.
244
+ * If at least one child is visible, the parent is shown. Propagates visibility changes up the tree.
245
+ */
246
+ const syncParentVisibilityWithChildren = (panelId, visibleMap) => {
247
+ const panel = panelMap.get(panelId);
248
+ if (!panel || panel.childrenIds.length === 0)
249
+ return;
250
+ const allChildrenHidden = panel.childrenIds.every(id => !visibleMap[id]);
251
+ if (allChildrenHidden && visibleMap[panelId]) {
252
+ visibleMap[panelId] = false;
253
+ if (panel.parentId) {
254
+ syncParentVisibilityWithChildren(panel.parentId, visibleMap);
255
+ }
256
+ }
257
+ else if (!allChildrenHidden && !visibleMap[panelId]) {
258
+ visibleMap[panelId] = true;
259
+ }
260
+ };
261
+ /**
262
+ * Toggles the maximized state of a panel based on its full ID.
263
+ * If the panel is already maximized (i.e., the maximized path matches its hierarchy path),
264
+ * it will be unmaximized. Otherwise, it will be maximized along with its ancestor panels.
265
+ * The full dot-separated ID of the panel (e.g., "root.section.panel")
266
+ */
267
+ const handleToggleMaximize = (fullId) => {
268
+ // Split the full panel ID into its segments (e.g., ["root", "section", "panel"])
269
+ const segments = fullId.split('.');
270
+ // Build the full path hierarchy up to this panel (e.g., ["root", "root.section", "root.section.panel"])
271
+ const path = segments.map((_, i) => segments.slice(0, i + 1).join('.'));
272
+ // Check if this panel is already maximized by comparing path arrays
273
+ const isAlreadyMaximized = maximizedPanelPath.length === path.length && maximizedPanelPath.every((v, i) => v === path[i]);
274
+ if (isAlreadyMaximized) {
275
+ // Unmaximize by clearing the maximized path
276
+ setMaximizedPanelPath([]);
277
+ }
278
+ else {
279
+ // Maximize this panel and its parent hierarchy
280
+ setMaximizedPanelPath(path);
281
+ }
282
+ };
283
+ /**
284
+ * Toggles the active (visible) state of a panel by its ID.
285
+ * - On **desktop**, the panel's visibility is toggled independently,
286
+ * and if the panel is hidden, all its child panels are also recursively hidden.
287
+ * - On **mobile**, only one panel can be visible at a time:
288
+ * activating a panel will hide all others, including their children.
289
+ * After updating visibility, the function also ensures that each parent panel's
290
+ * visibility is synchronized with the state of its children.
291
+ *
292
+ */
293
+ const handleTogglePanel = (id) => {
294
+ // If the panel is currently maximized, toggle its maximization state first
295
+ if (maximizedPanelPath.includes(id)) {
296
+ handleToggleMaximize(id);
297
+ }
298
+ // Update the activeButtons state
299
+ setActiveButtons(prev => {
300
+ // Clone the previous state to avoid mutating it directly
301
+ const newActive = { ...prev };
302
+ // Get the panel object from the panel map
303
+ const panel = panelMap.get(id);
304
+ // Determine if the current panel is currently visible
305
+ const currentlyVisible = !!newActive[id];
306
+ // Mobile-specific behavior: only one panel can be visible at a time
307
+ if (isMobile) {
308
+ // Hide all panels
309
+ Object.keys(newActive).forEach(panelId => {
310
+ newActive[panelId] = false;
311
+ });
312
+ // Toggle the selected panel's visibility
313
+ newActive[id] = !currentlyVisible;
314
+ // If the panel is being hidden and has children, recursively hide its children
315
+ if (panel && panel.childrenIds.length > 0 && !newActive[id]) {
316
+ const hideChildrenRecursively = (ids) => {
317
+ ids.forEach(childId => {
318
+ newActive[childId] = false;
319
+ const childPanel = panelMap.get(childId);
320
+ if (childPanel?.childrenIds.length) {
321
+ hideChildrenRecursively(childPanel.childrenIds);
322
+ }
323
+ });
324
+ };
325
+ hideChildrenRecursively(panel.childrenIds);
326
+ }
327
+ }
328
+ else {
329
+ // Desktop behavior: toggle visibility of the selected panel
330
+ if (!currentlyVisible) {
331
+ // If the panel is currently not visible (we are activating it)
332
+ if (panel?.alternativePanelIds.length) {
333
+ // If the panel has alternative panels defined
334
+ panel.alternativePanelIds.forEach(altId => {
335
+ // Deactivate each alternative panel
336
+ newActive[altId] = false;
337
+ });
338
+ }
339
+ // Activate the selected panel
340
+ newActive[id] = true;
341
+ }
342
+ else {
343
+ // The panel is currently visible, so we are deactivating it
344
+ newActive[id] = false;
345
+ }
346
+ // If hiding the panel and it has children, hide them recursively
347
+ if (panel && panel.childrenIds.length > 0 && !newActive[id]) {
348
+ const hideChildrenRecursively = (ids) => {
349
+ ids.forEach(childId => {
350
+ newActive[childId] = false;
351
+ const childPanel = panelMap.get(childId);
352
+ if (childPanel?.childrenIds.length) {
353
+ hideChildrenRecursively(childPanel.childrenIds);
354
+ }
355
+ });
356
+ };
357
+ hideChildrenRecursively(panel.childrenIds);
358
+ }
359
+ }
360
+ // After toggling, ensure the visibility of parent panels is in sync with their children
361
+ if (panel?.parentId) {
362
+ syncParentVisibilityWithChildren(panel.parentId, newActive);
363
+ }
364
+ // Return the updated visibility map
365
+ return newActive;
366
+ });
367
+ };
368
+ // Render panels
369
+ const renderPanels = (panels, parentPath = '', direction = 'horizontal') => {
370
+ const visiblePanels = panels.filter(p => {
371
+ const fullId = parentPath ? `${parentPath}.${p.id}` : p.id;
372
+ return p.contentOptions?.content && activeButtons[fullId];
373
+ });
374
+ const keys = visiblePanels.map(p => (parentPath ? `${parentPath}.${p.id}` : p.id));
375
+ const totalSize = keys.reduce((sum, key) => {
376
+ const entry = panelsState.get(key);
377
+ const size = parseFloat(direction === 'horizontal' ? entry?.width || '0' : entry?.height || '0');
378
+ return sum + size;
379
+ }, 0);
380
+ const sizeMap = Object.fromEntries(keys.map(key => {
381
+ const entry = panelsState.get(key);
382
+ const size = parseFloat(direction === 'horizontal' ? entry?.width || '0' : entry?.height || '0');
383
+ const percent = totalSize > 0 ? (size / totalSize) * 100 : 0;
384
+ return [key, `${percent}%`];
385
+ }));
386
+ return panels.map(panel => {
387
+ if (!panel.contentOptions)
388
+ return null;
389
+ const fullId = parentPath ? `${parentPath}.${panel.id}` : panel.id;
390
+ const isActive = activeButtons[fullId];
391
+ const hasChildren = isPanelArray(panel.contentOptions?.content);
392
+ const isLastVisible = keys.indexOf(fullId) === keys.length - 1;
393
+ const flexDirection = direction === 'horizontal' ? 'row' : 'column';
394
+ const panelStyle = {
395
+ display: 'flex',
396
+ flexDirection,
397
+ width: direction === 'horizontal' ? (isActive ? sizeMap[fullId] : '0%') : '100%',
398
+ height: direction === 'vertical' ? (isActive ? sizeMap[fullId] : '0%') : '100%',
399
+ visibility: isActive ? 'visible' : 'hidden',
400
+ overflow: 'hidden',
401
+ position: 'relative',
402
+ };
403
+ const contentStyle = {
404
+ flexGrow: 1,
405
+ overflow: 'auto',
406
+ };
407
+ return (_jsxs("div", { ref: el => {
408
+ panelRefs.current[fullId] = el;
409
+ }, style: panelStyle, children: [_jsx("div", { style: contentStyle, children: hasChildren
410
+ ? renderPanels(panel.contentOptions.content, fullId, 'vertical')
411
+ : panel.contentOptions?.panelContainer ? _jsx(TMPanel, { title: panel.contentOptions.panelContainer.title, totalItems: panel.contentOptions.panelContainer.totalItems, displayedItemsCount: panel.contentOptions.panelContainer.displayedItemsCount, onClose: () => handleTogglePanel(fullId), onMaximize: () => handleToggleMaximize(fullId), allowMaximize: !isMobile, children: typeof panel.contentOptions.content === "function" ? panel.contentOptions.content(handleTogglePanel, handleToggleMaximize, handleVisibilityButton, handleDisableButton) : panel.contentOptions.content })
412
+ :
413
+ typeof panel.contentOptions.content === "function" ? panel.contentOptions.content(handleTogglePanel, handleToggleMaximize, handleVisibilityButton, handleDisableButton) : panel.contentOptions.content }), !isLastVisible && isActive && maximizedPanelPath.length === 0 && (_jsx("div", { style: {
414
+ cursor: direction === 'horizontal' ? 'col-resize' : 'row-resize',
415
+ userSelect: 'none',
416
+ flexShrink: 0,
417
+ backgroundColor: 'transparent',
418
+ width: direction === 'horizontal' ? '4px' : '100%',
419
+ height: direction === 'vertical' ? '4px' : '100%',
420
+ marginLeft: maximizedPanelPath.length === 0 && direction === 'horizontal' ? `${(gutters - 4) / 2}px` : undefined,
421
+ marginTop: maximizedPanelPath.length === 0 && direction === 'vertical' ? `${(gutters - 4) / 2}px` : undefined,
422
+ marginRight: maximizedPanelPath.length === 0 && direction === 'horizontal' ? `${(gutters - 4) / 2}px` : undefined,
423
+ marginBottom: maximizedPanelPath.length === 0 && direction === 'vertical' ? `${(gutters - 4) / 2}px` : undefined,
424
+ }, onMouseDown: () => {
425
+ if (direction === 'horizontal') {
426
+ onColumnMouseDown(fullId);
427
+ }
428
+ else {
429
+ onRowMouseDown(fullId);
430
+ }
431
+ } }))] }, fullId));
432
+ });
433
+ };
434
+ return (_jsxs("div", { style: { display: 'flex', flexDirection: isMobile ? 'column' : 'row', height: '100%', width: '100%' }, children: [_jsx("div", { style: {
435
+ display: 'flex',
436
+ flexGrow: 1,
437
+ width: isAtLeastOnePanelVisible(activeButtons) ? `calc(100% - ${showToolbar ? (isMobile ? 0 : 60) : 0}px)` : '0%',
438
+ height: isAtLeastOnePanelVisible(activeButtons) ? `calc(100% - ${isMobile ? 60 : 0}px)` : '0%',
439
+ visibility: isAtLeastOnePanelVisible(activeButtons) ? 'visible' : 'hidden',
440
+ flexDirection: 'row',
441
+ }, children: renderPanels(panels, '', 'horizontal') }), !isAtLeastOnePanelVisible(activeButtons) && (_jsxs("div", { style: {
442
+ width: '100%',
443
+ height: isMobile ? 'calc(100% - 60px)' : '100%',
444
+ display: 'flex',
445
+ flexDirection: 'column',
446
+ alignItems: 'center',
447
+ justifyContent: 'center',
448
+ backgroundColor: '#fff',
449
+ borderRadius: '12px',
450
+ textAlign: 'center',
451
+ color: '#555',
452
+ fontSize: '18px',
453
+ }, children: [_jsx(IconInfo, { style: { fontSize: 50 } }), _jsx("div", { children: SDKUI_Localizator.NoPanelSelected })] })), showToolbar && _jsx("div", { style: {
454
+ width: isMobile ? '100%' : '60px',
455
+ height: isMobile ? '60px' : '100%',
456
+ borderLeft: '1px solid #ccc',
457
+ display: 'flex',
458
+ flexDirection: isMobile ? 'row' : 'column',
459
+ padding: '8px',
460
+ boxSizing: 'border-box',
461
+ gap: '6px',
462
+ backgroundColor: '#f9f9f9',
463
+ }, children: _jsx(TMPanelManagerToolbar, { allPanels: allPanels, activeButtons: activeButtons, handleTogglePanel: handleTogglePanel, updatedDisabledButton: updatedDisabledButton, updatedVisibleButton: updatedVisibleButton }) })] }));
464
+ };
465
+ export default TMPanelManager;
@@ -0,0 +1,16 @@
1
+ import { TMPanelEntry } from './TMPanelManagerUtils';
2
+ interface TNPanelManagerToolbarProps {
3
+ allPanels: Array<TMPanelEntry>;
4
+ activeButtons: Record<string, boolean>;
5
+ handleTogglePanel: (id: string) => void;
6
+ updatedDisabledButton?: {
7
+ id: string;
8
+ value: boolean;
9
+ };
10
+ updatedVisibleButton?: {
11
+ id: string;
12
+ value: boolean;
13
+ };
14
+ }
15
+ declare const TMPanelManagerToolbar: (props: TNPanelManagerToolbarProps) => import("react/jsx-runtime").JSX.Element;
16
+ export default TMPanelManagerToolbar;
@@ -0,0 +1,100 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useEffect, useMemo, useState } from 'react';
3
+ import { useDeviceType, DeviceType } from './TMDeviceProvider';
4
+ import TMTooltip from './TMTooltip';
5
+ const TMPanelManagerToolbar = (props) => {
6
+ const { allPanels, activeButtons, handleTogglePanel, updatedDisabledButton, updatedVisibleButton } = props;
7
+ const deviceType = useDeviceType();
8
+ let isMobile = useMemo(() => { return deviceType === DeviceType.MOBILE; }, [deviceType]);
9
+ const [filteredPanels, setFilteredPanels] = useState([]);
10
+ const [disabledButtons, setDisabledButtons] = useState({});
11
+ const [visibleButtons, setVisibleButtons] = useState({});
12
+ // Initialize visibility and disabled states
13
+ useEffect(() => {
14
+ const visible = {};
15
+ const disabled = {};
16
+ const filtered = allPanels
17
+ .filter((p) => {
18
+ const vis = !!p.config.toolbarOptions?.visible;
19
+ visible[p.fullId] = vis;
20
+ disabled[p.fullId] = !!p.config.toolbarOptions?.disabled;
21
+ return vis;
22
+ })
23
+ .sort((a, b) => (a.config.toolbarOptions?.orderNumber ?? 0) - (b.config.toolbarOptions?.orderNumber ?? 0));
24
+ setVisibleButtons(visible);
25
+ setDisabledButtons(disabled);
26
+ setFilteredPanels(filtered);
27
+ }, [allPanels]);
28
+ // Update disabled button
29
+ useEffect(() => {
30
+ if (updatedDisabledButton) {
31
+ setDisabledButtons((prev) => ({
32
+ ...prev,
33
+ [updatedDisabledButton.id]: updatedDisabledButton.value,
34
+ }));
35
+ }
36
+ }, [updatedDisabledButton]);
37
+ // Update visible button
38
+ useEffect(() => {
39
+ if (updatedVisibleButton) {
40
+ const { id, value } = updatedVisibleButton;
41
+ setVisibleButtons((prev) => ({ ...prev, [id]: value }));
42
+ setFilteredPanels((prev) => {
43
+ if (value) {
44
+ const newPanel = allPanels.find((p) => p.fullId === id);
45
+ if (newPanel && !prev.some((p) => p.fullId === id)) {
46
+ return [...prev, newPanel].sort((a, b) => (a.config.toolbarOptions?.orderNumber ?? 0) - (b.config.toolbarOptions?.orderNumber ?? 0));
47
+ }
48
+ return prev;
49
+ }
50
+ else {
51
+ return prev.filter((p) => p.fullId !== id);
52
+ }
53
+ });
54
+ }
55
+ }, [updatedVisibleButton, allPanels]);
56
+ return (_jsx("div", { style: {
57
+ display: 'flex',
58
+ flexDirection: isMobile ? 'row' : 'column',
59
+ gap: '6px',
60
+ alignItems: isMobile ? 'center' : 'flex-start',
61
+ justifyContent: isMobile ? 'center' : 'flex-start',
62
+ width: '100%',
63
+ height: '100%',
64
+ }, children: filteredPanels.map((p, idx) => {
65
+ const isActive = activeButtons[p.fullId];
66
+ const handleClick = p.config.buttonOptions?.onClick;
67
+ const isToolbarDisabled = disabledButtons[p.fullId];
68
+ // Separator JSX if beginGroup is true and NOT first button (otherwise no separator before first button)
69
+ const separator = (p.config.toolbarOptions?.beginGroup && idx > 0) ? (_jsx("div", { style: {
70
+ width: isMobile ? '1px' : '80%',
71
+ height: isMobile ? '80%' : '1px',
72
+ backgroundColor: '#ccc',
73
+ margin: isMobile ? '0 6px' : '6px 0',
74
+ alignSelf: 'center',
75
+ } }, `sep-${p.fullId}`)) : null;
76
+ // Button JSX
77
+ const button = (_jsx(TMTooltip, { content: p.config.name, position: 'left', children: _jsx("button", { disabled: isToolbarDisabled, onClick: () => { handleTogglePanel(p.fullId); if (handleClick)
78
+ handleClick(); }, style: {
79
+ display: 'flex',
80
+ alignItems: 'center',
81
+ justifyContent: 'center',
82
+ height: '40px',
83
+ width: '40px',
84
+ border: 'none',
85
+ borderRadius: '10px',
86
+ fontSize: '18px',
87
+ padding: '8px 10px',
88
+ color: '#fff',
89
+ transition: 'transform 0.2s ease, box-shadow 0.2s ease',
90
+ cursor: isToolbarDisabled ? 'not-allowed' : 'pointer',
91
+ opacity: isToolbarDisabled ? 0.6 : 1,
92
+ backgroundColor: isActive ? '#81c784' : '#e57373',
93
+ }, onMouseEnter: (e) => { if (!isToolbarDisabled) {
94
+ e.currentTarget.style.transform = 'scale(1.1)';
95
+ } }, onMouseLeave: (e) => { e.currentTarget.style.transform = 'scale(1)'; }, children: typeof p.config.toolbarOptions?.icon === 'string' ? (_jsx("i", { className: `dx-icon dx-icon-${p.config.toolbarOptions?.icon}` })) : (p.config.toolbarOptions?.icon) }) }, p.fullId));
96
+ // Return separator first, then button
97
+ return [separator, button];
98
+ }) }));
99
+ };
100
+ export default TMPanelManagerToolbar;
@@ -0,0 +1,36 @@
1
+ import { ITMPanelProps } from "./TMPanel";
2
+ export type TMPanelItemConfig = {
3
+ id: string;
4
+ name: string;
5
+ type: 'content' | 'button';
6
+ alternativePanelIds?: Array<string>;
7
+ buttonOptions?: {
8
+ onClick: () => void;
9
+ };
10
+ contentOptions?: {
11
+ height: string;
12
+ width: string;
13
+ visible?: boolean;
14
+ content?: JSX.Element | ((handleTogglePanel: (id: string) => void, handleToggleMaximize: (fullId: string) => void, handleVisibilityButton: (id: string, value: boolean) => void, handleDisableButton: (id: string, value: boolean) => void) => JSX.Element) | Array<TMPanelItemConfig>;
15
+ panelContainer?: ITMPanelProps;
16
+ };
17
+ toolbarOptions?: {
18
+ icon: string | JSX.Element;
19
+ isActive: boolean;
20
+ visible: boolean;
21
+ disabled?: boolean;
22
+ orderNumber?: number;
23
+ beginGroup?: boolean;
24
+ };
25
+ };
26
+ export type TMPanelEntry = {
27
+ fullId: string;
28
+ config: TMPanelItemConfig;
29
+ parentId: string | null;
30
+ childrenIds: Array<string>;
31
+ alternativePanelIds: Array<string>;
32
+ };
33
+ export declare const isPanelArray: (content: unknown) => content is Array<TMPanelItemConfig>;
34
+ export declare const flattenPanels: (panels: Array<TMPanelItemConfig>, parentId?: string | null, parentPath?: string) => Array<TMPanelEntry>;
35
+ export declare const isAtLeastOnePanelVisible: (activeButtons: Record<string, boolean>) => boolean;
36
+ export declare const isExactlyOnePanelVisible: (activeButtons: Record<string, boolean>) => boolean;
@@ -0,0 +1,27 @@
1
+ // Type guard to check if a value is an array of TMPanelItemConfig objects.
2
+ // Ensures that every item in the array is a non-null object with 'id' and 'contentOptions' properties.
3
+ export const isPanelArray = (content) => Array.isArray(content) && content.every(p => typeof p === 'object' && p !== null && 'id' in p && 'contentOptions' in p);
4
+ // Recursively flattens a nested array of panel configurations into a flat array of TMPanelEntry objects.
5
+ // - panels: the input array of TMPanelItemConfig.
6
+ // - parentId: optional ID of the parent panel (used for hierarchical relationships).
7
+ // - parentPath: string path of ancestor IDs, used to build a full, unique identifier for each panel.
8
+ export const flattenPanels = (panels, parentId = null, parentPath = '') => {
9
+ return panels.flatMap(panel => {
10
+ // Construct the full ID by appending the current panel ID to the parent path.
11
+ const fullId = parentPath ? `${parentPath}.${panel.id}` : panel.id;
12
+ let children = [];
13
+ // If the panel has nested content that is also a valid panel array, recursively flatten them.
14
+ if (isPanelArray(panel.contentOptions?.content)) {
15
+ children = flattenPanels(panel.contentOptions.content, fullId, fullId);
16
+ }
17
+ // Extract full IDs of all child entries.
18
+ const childrenIds = children.map(c => c.fullId);
19
+ const alternativePanelIds = panel.alternativePanelIds ?? [];
20
+ // Return the current panel as a TMPanelEntry followed by its flattened children.
21
+ return [{ fullId, config: panel, parentId, childrenIds, alternativePanelIds }, ...children];
22
+ });
23
+ };
24
+ // Checks if at least one panel is currently visible (i.e., at least one active button is true)
25
+ export const isAtLeastOnePanelVisible = (activeButtons) => Object.values(activeButtons).some(Boolean);
26
+ // Checks if exactly one panel is currently visible (i.e., only one active button is true)
27
+ export const isExactlyOnePanelVisible = (activeButtons) => Object.values(activeButtons).filter(Boolean).length === 1;
@@ -14,8 +14,9 @@ export * from './base/TMToolbarCard';
14
14
  export * from './base/TMRightSidebar';
15
15
  export * from './base/TMTreeView';
16
16
  export * from './base/TMPanel';
17
- export { default as TMPanelManagerMatrix } from './base/TMPanelManagerMatrix';
18
- export * from './base/TMPanelManagerMatrixUtils';
17
+ export { default as TMPanelManager } from './base/TMPanelManager';
18
+ export { default as TMPanelManagerToolbar } from './base/TMPanelManagerToolbar';
19
+ export * from './base/TMPanelManagerUtils';
19
20
  export { default as CounterBar } from './base/TMCounterBar';
20
21
  export { default as TMProgressBar } from './base/TMProgressBar';
21
22
  export { default as TMSpinner } from './base/TMSpinner';
@@ -15,8 +15,9 @@ export * from './base/TMToolbarCard';
15
15
  export * from './base/TMRightSidebar';
16
16
  export * from './base/TMTreeView';
17
17
  export * from './base/TMPanel';
18
- export { default as TMPanelManagerMatrix } from './base/TMPanelManagerMatrix';
19
- export * from './base/TMPanelManagerMatrixUtils';
18
+ export { default as TMPanelManager } from './base/TMPanelManager';
19
+ export { default as TMPanelManagerToolbar } from './base/TMPanelManagerToolbar';
20
+ export * from './base/TMPanelManagerUtils';
20
21
  export { default as CounterBar } from './base/TMCounterBar';
21
22
  export { default as TMProgressBar } from './base/TMProgressBar';
22
23
  export { default as TMSpinner } from './base/TMSpinner';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@topconsultnpm/sdkui-react-beta",
3
- "version": "6.13.33",
3
+ "version": "6.13.35",
4
4
  "description": "",
5
5
  "scripts": {
6
6
  "test": "echo \"Error: no test specified\" && exit 1",
@@ -1,9 +0,0 @@
1
- import { TMPanelManagerMatrixColumn, TMPanelManagerToolbar } from './TMPanelManagerMatrixUtils';
2
- export interface TMPanelManagerMatrixProps {
3
- panelMatrixMap: Map<string, TMPanelManagerMatrixColumn>;
4
- toolbar?: TMPanelManagerToolbar;
5
- initialMobilePanelID?: string;
6
- gutters?: number;
7
- }
8
- declare const TMPanelManagerMatrix: (props: TMPanelManagerMatrixProps) => import("react/jsx-runtime").JSX.Element;
9
- export default TMPanelManagerMatrix;
@@ -1,262 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import React, { useEffect, useMemo, useState } from 'react';
3
- import { SDKUI_Globals, SDKUI_Localizator, IconInfo } from '../../helper';
4
- import { useDeviceType, DeviceType } from './TMDeviceProvider';
5
- import TMPanel from './TMPanel';
6
- import { calculatePanelSizes, DesktopToolbar, getDynamicColumnWidth, getInitialMobilePanelID, MobileToolbar } from './TMPanelManagerMatrixUtils';
7
- const TMPanelManagerMatrix = (props) => {
8
- // Destructure props
9
- const { panelMatrixMap, toolbar, initialMobilePanelID, gutters = SDKUI_Globals.userSettings.themeSettings.gutters } = props;
10
- // Get the current device type (e.g., mobile, tablet, desktop) using a custom hook.
11
- const deviceType = useDeviceType();
12
- // This avoids unnecessary re-renders by only recalculating when deviceType changes.
13
- let isMobile = useMemo(() => { return deviceType === DeviceType.MOBILE; }, [deviceType]);
14
- // State for toolbar items
15
- const [toolbarState, setToolbarState] = useState(toolbar);
16
- // State to track which panel IDs are currently hidden
17
- const [hiddenPanelIds, setHiddenPanelIds] = useState(() => {
18
- const initialHidden = new Set();
19
- if (isMobile) {
20
- // Mobile: mostra solo il primo pannello
21
- let firstMobilePanelID = initialMobilePanelID ?? getInitialMobilePanelID(panelMatrixMap);
22
- panelMatrixMap.forEach(col => {
23
- col.rows.forEach(row => {
24
- if (row.id !== firstMobilePanelID)
25
- initialHidden.add(row.id);
26
- });
27
- });
28
- }
29
- else {
30
- // Desktop: nascondi quelli con hidden true
31
- panelMatrixMap.forEach(col => {
32
- col.rows.forEach(row => {
33
- if (row.hidden)
34
- initialHidden.add(row.id);
35
- });
36
- });
37
- }
38
- return initialHidden;
39
- });
40
- // State to store the current widths of all column, represented as a Map
41
- const [columnWidths, setColumnWidths] = useState(new Map());
42
- // State to store the current heights of all panels, represented as a Map
43
- const [panelHeights, setPanelHeights] = useState(new Map());
44
- // State to track maximized panel
45
- const [maximizedPanelId, setMaximizedPanelId] = useState(null);
46
- // Memoized computation of visible columns and the total number of visible panels
47
- const { visibleColumns, totalVisiblePanels } = useMemo(() => {
48
- let totalVisiblePanels = 0;
49
- const visibleColumns = [];
50
- for (const [columnKey, column] of panelMatrixMap.entries()) {
51
- // Filter visible rows in the current column
52
- const visibleRows = column.rows.filter(row => !hiddenPanelIds.has(row.id));
53
- // If any visible rows exist, include the column and count them
54
- if (visibleRows.length > 0) {
55
- visibleColumns.push(columnKey);
56
- totalVisiblePanels += visibleRows.length;
57
- }
58
- }
59
- return { visibleColumns, totalVisiblePanels };
60
- }, [panelMatrixMap, hiddenPanelIds]);
61
- // Effect hook to update column widths
62
- useEffect(() => {
63
- const newColumnWidths = new Map();
64
- Array.from(panelMatrixMap.entries()).forEach(([columnKey, col]) => {
65
- if (!maximizedPanelId) {
66
- const width = getDynamicColumnWidth(col, hiddenPanelIds, Array.from(panelMatrixMap.values()));
67
- newColumnWidths.set(columnKey, { width });
68
- }
69
- });
70
- setColumnWidths(newColumnWidths);
71
- }, [panelMatrixMap, hiddenPanelIds, maximizedPanelId]);
72
- // Effect hook to set initial hidden panels and their heights
73
- useEffect(() => {
74
- const initialHidden = new Set();
75
- if (isMobile) {
76
- // On mobile, only show the first selected mobile panel; hide the rest
77
- let firstMobilePanelID = initialMobilePanelID ?? getInitialMobilePanelID(panelMatrixMap);
78
- panelMatrixMap.forEach(col => {
79
- col.rows.forEach(row => {
80
- if (row.id !== firstMobilePanelID)
81
- initialHidden.add(row.id);
82
- });
83
- });
84
- }
85
- else {
86
- // On non-mobile devices, hide panels that are marked as `hidden`
87
- panelMatrixMap.forEach(col => {
88
- col.rows.forEach(row => {
89
- if (row.hidden)
90
- initialHidden.add(row.id);
91
- });
92
- });
93
- }
94
- // Update state with the calculated hidden panels and panel sizes
95
- setHiddenPanelIds(initialHidden);
96
- setPanelHeights(calculatePanelSizes(panelMatrixMap, initialHidden));
97
- }, [panelMatrixMap, isMobile]);
98
- // Function to toggle the disabled of a toolbar given its item
99
- const toggleToolbarItemDisabled = (toolbarItem) => {
100
- if (!toolbarItem)
101
- return;
102
- setToolbarState(prevToolbar => {
103
- if (!prevToolbar)
104
- return prevToolbar;
105
- return {
106
- ...prevToolbar,
107
- items: prevToolbar.items.map(item => item.id === toolbarItem.id ? { ...item, disabled: !item.disabled } : item)
108
- };
109
- });
110
- };
111
- // Function to toggle the visibility of a toolbar given its item
112
- const toggleToolbarItemVisibility = (toolbarItem) => {
113
- if (!toolbarItem)
114
- return;
115
- setToolbarState(prevToolbar => {
116
- if (!prevToolbar)
117
- return prevToolbar;
118
- return {
119
- ...prevToolbar,
120
- items: prevToolbar.items.map(item => item.id === toolbarItem.id ? { ...item, visible: !(item.visible ?? true) } : item)
121
- };
122
- });
123
- };
124
- // Function to toggle the visibility of a panel given its item
125
- const togglePanelVisibility = (toolbarItem) => {
126
- if (!toolbarItem)
127
- return;
128
- setHiddenPanelIds(prevHidden => {
129
- let newHidden;
130
- if (isMobile) {
131
- // On mobile, make only the selected panel visible; hide all others
132
- newHidden = new Set();
133
- panelMatrixMap.forEach(col => {
134
- col.rows.forEach(row => {
135
- if (row.id !== toolbarItem.panelManagerMatrixRowId) {
136
- newHidden.add(row.id);
137
- }
138
- });
139
- });
140
- }
141
- else {
142
- // On non-mobile devices, toggle visibility normally
143
- newHidden = new Set(prevHidden);
144
- // Hide alternative panels if any
145
- if (toolbarItem.alternativePanelManagerMatrixRowId?.length) {
146
- toolbarItem.alternativePanelManagerMatrixRowId.forEach(altId => newHidden.add(altId));
147
- }
148
- // Toggle clicked panel visibility
149
- if (newHidden.has(toolbarItem.panelManagerMatrixRowId)) {
150
- newHidden.delete(toolbarItem.panelManagerMatrixRowId);
151
- }
152
- else {
153
- // If the panel is maximized, call maximizeMinimizePanelCallback to minimize it
154
- if (maximizedPanelId === toolbarItem.panelManagerMatrixRowId) {
155
- toogleMaximizeMinimizePanelCallback(toolbarItem.panelManagerMatrixRowId);
156
- }
157
- newHidden.add(toolbarItem.panelManagerMatrixRowId);
158
- }
159
- }
160
- setPanelHeights(calculatePanelSizes(panelMatrixMap, newHidden));
161
- return newHidden;
162
- });
163
- };
164
- const toogleMaximizeMinimizePanelCallback = (id) => {
165
- setMaximizedPanelId(prevId => (prevId === id ? null : id));
166
- };
167
- return (_jsx("div", { style: { width: '100%', height: '100%' }, children: _jsxs("div", { style: {
168
- display: isMobile ? 'block' : 'flex',
169
- flexDirection: 'row',
170
- width: "100%",
171
- height: '100%',
172
- overflow: 'hidden',
173
- position: 'relative',
174
- }, children: [_jsx("div", { style: {
175
- display: 'flex',
176
- flexDirection: 'row',
177
- width: (toolbarState && toolbarState.items.length > 0) ? `calc(100% - ${isMobile ? "0px" : "60px"})` : '100%',
178
- height: `calc(100% - ${isMobile ? "60px" : "0px"})`,
179
- overflow: 'hidden',
180
- // transition: 'width 0.3s',
181
- }, children: visibleColumns.length > 0 ? visibleColumns.map((column, colIndex) => {
182
- // Retrieve the column data from the panelMatrixMap using the current column key
183
- const col = panelMatrixMap.get(column);
184
- // Check if the current column contains the maximized panel
185
- const hasMaximized = !!col?.rows?.some(row => row.id === maximizedPanelId);
186
- const isLastColumn = colIndex === visibleColumns.length - 1;
187
- // Define the base style for all columns with flex layout, full height, smooth width transition, and no minimum width
188
- const columnBaseStyle = {
189
- display: 'flex',
190
- flexDirection: 'column',
191
- height: '100%',
192
- // transition: isMobile ? 'none' : 'width 0.3s',
193
- paddingRight: (!maximizedPanelId && !isLastColumn) ? `${gutters}px` : '0px'
194
- };
195
- // Declare a variable to hold the final computed style for the column
196
- let columnStyle;
197
- if (maximizedPanelId) {
198
- // If a panel is maximized, if it does, give the column full width
199
- columnStyle = hasMaximized ? { ...columnBaseStyle, width: '100%' } : { ...columnBaseStyle, width: '0px', overflow: 'hidden' };
200
- }
201
- else {
202
- // Otherwise, hide this column by setting width to 0 and hiding overflow
203
- const savedWidth = columnWidths.get(column)?.width ?? getDynamicColumnWidth(col, hiddenPanelIds, Array.from(panelMatrixMap.values()));
204
- columnStyle = { ...columnBaseStyle, width: savedWidth };
205
- }
206
- return (_jsx(React.Fragment, { children: _jsx("div", { style: columnStyle, children: col?.rows?.map((row) => {
207
- const isHiddenPanel = hiddenPanelIds.has(row.id);
208
- const visibleRows = col.rows?.filter(r => !hiddenPanelIds.has(r.id));
209
- const isLastVisible = visibleRows.length > 0 && visibleRows[visibleRows.length - 1].id === row.id;
210
- const isMaximized = maximizedPanelId === row.id;
211
- const baseStyle = {
212
- borderRadius: '8px',
213
- display: 'block',
214
- // transition: isMobile ? 'none' : 'all 0.3s ease',
215
- paddingBottom: (isHiddenPanel || isLastVisible || isMaximized) ? '0px' : `${gutters}px`,
216
- };
217
- const hiddenStyle = {
218
- minWidth: '0px',
219
- width: '0px',
220
- height: '0px',
221
- visibility: 'hidden',
222
- opacity: 0,
223
- };
224
- const maximizedStyle = {
225
- minWidth: '50px',
226
- width: '100%',
227
- height: '100%',
228
- visibility: 'visible',
229
- opacity: 1,
230
- };
231
- const defaultStyle = {
232
- minWidth: isHiddenPanel ? '0px' : '50px',
233
- width: '100%',
234
- height: isHiddenPanel ? '0px' : panelHeights.get(row.id)?.height ?? '100%',
235
- visibility: isHiddenPanel ? 'hidden' : 'visible',
236
- opacity: isHiddenPanel ? 0 : 1,
237
- };
238
- const panelStyle = maximizedPanelId ? (isMaximized ? { ...baseStyle, ...maximizedStyle } : hiddenStyle) : { ...baseStyle, ...defaultStyle };
239
- return _jsx("div", { style: panelStyle, children: row.panel ? _jsx(TMPanel, { title: row.panel.title, totalItems: row.panel.totalItems ?? undefined, displayedItemsCount: row.panel.displayedItemsCount ?? undefined, showHeader: row.panel.showHeader ?? true, allowMaximize: !isMobile, onClose: (!isMobile && toolbarState) ? () => togglePanelVisibility(toolbarState.items.find(item => item.panelManagerMatrixRowId === row.id)) : undefined, onMaximize: () => toogleMaximizeMinimizePanelCallback(row.id), onHeaderDoubleClick: () => toogleMaximizeMinimizePanelCallback(row.id), children: _jsx("div", { style: { width: '100%', height: '100%' }, children: typeof row.content === "function" ? row.content(togglePanelVisibility, toogleMaximizeMinimizePanelCallback, toggleToolbarItemDisabled, toggleToolbarItemVisibility) : row.content }) })
240
- : _jsx("div", { style: { width: '100%', height: '100%' }, children: typeof row.content === "function" ? row.content(togglePanelVisibility, toogleMaximizeMinimizePanelCallback, toggleToolbarItemDisabled, toggleToolbarItemVisibility) : row.content }) }, "TMPanelManagerMatrix-r-" + row.id);
241
- }) }) }, "TMPanelManagerMatrix-c-" + column));
242
- }) : _jsxs("div", { style: {
243
- width: '100%',
244
- height: '100%',
245
- display: 'flex',
246
- flexDirection: 'column',
247
- alignItems: 'center',
248
- justifyContent: 'center',
249
- backgroundColor: '#fff',
250
- border: '1px solid #ddd',
251
- borderRadius: '12px',
252
- boxShadow: '0 4px 10px rgba(0, 0, 0, 0.05)',
253
- padding: '40px',
254
- textAlign: 'center',
255
- color: '#555',
256
- fontSize: '18px',
257
- gap: '16px'
258
- }, children: [_jsx(IconInfo, { style: { fontSize: 50 } }), _jsx("div", { children: SDKUI_Localizator.NoPanelSelected })] }) }), toolbarState && toolbarState.items.length > 0 && (!isMobile
259
- ? DesktopToolbar(toolbarState, hiddenPanelIds, togglePanelVisibility)
260
- : MobileToolbar(toolbarState, hiddenPanelIds, togglePanelVisibility))] }) }));
261
- };
262
- export default TMPanelManagerMatrix;
@@ -1,69 +0,0 @@
1
- import { ITMPanelProps } from './TMPanel';
2
- export declare const defaultTMPanelManagerToolbarStyles: {
3
- container: {
4
- backgroundColor: string;
5
- padding: string;
6
- };
7
- items: {
8
- width: string;
9
- height: string;
10
- inactiveBackgroundColor: string;
11
- activeBackgroundColor: string;
12
- borderRadius: string;
13
- marginBottom: string;
14
- iconFontSize: string;
15
- iconColor: string;
16
- };
17
- };
18
- export interface TMPanelManagerMatrixColumn {
19
- rows: Array<TMPanelManagerMatrixRow>;
20
- width?: string;
21
- }
22
- export interface ExtendedTMPanelProps extends ITMPanelProps {
23
- showCustomMaximizeMinimizeButton?: boolean;
24
- }
25
- export interface TMPanelManagerMatrixRow {
26
- id: string;
27
- hidden: boolean;
28
- content: JSX.Element | ((togglePanelVisibility: (toolbarItem: TMPanelManagerToolbarItem | undefined) => void, toogleMaximizeMinimizePanelCallback: (id: string) => void, toggleToolbarItemDisabled: (toolbarItem: TMPanelManagerToolbarItem | undefined) => void, toggleToolbarItemVisibility: (toolbarItem: TMPanelManagerToolbarItem | undefined) => void) => JSX.Element);
29
- height?: string;
30
- width?: string;
31
- panel?: ExtendedTMPanelProps;
32
- }
33
- export interface TMPanelManagerToolbarStyles {
34
- container?: {
35
- backgroundColor?: string;
36
- padding?: string;
37
- };
38
- items?: {
39
- width?: string;
40
- height?: string;
41
- inactiveBackgroundColor?: string;
42
- activeBackgroundColor?: string;
43
- borderRadius?: string;
44
- marginBottom?: string;
45
- iconFontSize?: string;
46
- iconColor?: string;
47
- };
48
- }
49
- export interface TMPanelManagerToolbarItem {
50
- id: string;
51
- icon: string | JSX.Element;
52
- tooltipName: string;
53
- panelManagerMatrixRowId: string;
54
- alternativePanelManagerMatrixRowId?: Array<string>;
55
- beginGroup?: boolean;
56
- disabled?: boolean;
57
- visible?: boolean;
58
- }
59
- export interface TMPanelManagerToolbar {
60
- items: Array<TMPanelManagerToolbarItem>;
61
- styles?: TMPanelManagerToolbarStyles;
62
- }
63
- export declare const calculatePanelSizes: (panelMatrixMap: Map<string, TMPanelManagerMatrixColumn>, hiddenIds: Set<string>) => Map<string, {
64
- height: string;
65
- }>;
66
- export declare const getInitialMobilePanelID: (panelMatrixMap: Map<string, TMPanelManagerMatrixColumn>) => string | undefined;
67
- export declare const DesktopToolbar: (toolbar: TMPanelManagerToolbar, hiddenPanelIds: Set<string>, togglePanelVisibility: (panel: TMPanelManagerToolbarItem) => void) => import("react/jsx-runtime").JSX.Element;
68
- export declare const MobileToolbar: (toolbar: TMPanelManagerToolbar, hiddenPanelIds: Set<string>, togglePanelVisibility: (panel: TMPanelManagerToolbarItem) => void) => import("react/jsx-runtime").JSX.Element;
69
- export declare const getDynamicColumnWidth: (col: TMPanelManagerMatrixColumn | undefined, hiddenPanelIds: Set<string>, allColumns: Array<TMPanelManagerMatrixColumn>) => string;
@@ -1,155 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { ScrollView } from "devextreme-react";
3
- import { SDKUI_Globals } from '../../helper';
4
- import TMTooltip from './TMTooltip';
5
- export const defaultTMPanelManagerToolbarStyles = {
6
- container: {
7
- backgroundColor: '#f0f0f0',
8
- padding: '6px'
9
- },
10
- items: {
11
- width: '35px',
12
- height: '35px',
13
- inactiveBackgroundColor: '#e57373',
14
- activeBackgroundColor: '#81c784',
15
- borderRadius: '50%',
16
- marginBottom: '6px',
17
- iconFontSize: "25px",
18
- iconColor: '#ffffff'
19
- }
20
- };
21
- ;
22
- // Helper to calculate sizes based on hidden panel IDs
23
- export const calculatePanelSizes = (panelMatrixMap, hiddenIds) => {
24
- const sizes = new Map();
25
- panelMatrixMap.forEach((column) => {
26
- const visiblePanels = column.rows.filter(p => !hiddenIds.has(p.id));
27
- const hiddenPanels = column.rows.filter(p => hiddenIds.has(p.id));
28
- if (visiblePanels.length > 0) {
29
- const totalVisibleHeight = visiblePanels.reduce((sum, panel) => {
30
- return sum + parseFloat(panel.height || '0');
31
- }, 0);
32
- visiblePanels.forEach(panel => {
33
- const originalHeight = parseFloat(panel.height || '0');
34
- const newHeightPercent = (originalHeight / totalVisibleHeight) * 100;
35
- sizes.set(panel.id, { height: `${newHeightPercent}%` });
36
- });
37
- }
38
- hiddenPanels.forEach(panel => {
39
- sizes.set(panel.id, { height: '0%' });
40
- });
41
- });
42
- return sizes;
43
- };
44
- export const getInitialMobilePanelID = (panelMatrixMap) => {
45
- const firstEntry = panelMatrixMap.values().next().value?.rows;
46
- if (!firstEntry)
47
- return undefined;
48
- const visiblePanel = firstEntry.find((row) => !row.hidden);
49
- return visiblePanel?.id;
50
- };
51
- export const DesktopToolbar = (toolbar, hiddenPanelIds, togglePanelVisibility) => {
52
- return _jsx("div", { style: {
53
- width: '40px',
54
- display: 'flex',
55
- flexDirection: 'column',
56
- justifyContent: 'flex-start',
57
- borderRadius: '8px',
58
- alignItems: 'center',
59
- marginLeft: SDKUI_Globals.userSettings.themeSettings.gutters,
60
- // customizable properties
61
- backgroundColor: toolbar.styles?.container?.backgroundColor ?? defaultTMPanelManagerToolbarStyles.container.backgroundColor,
62
- padding: toolbar.styles?.container?.padding ?? defaultTMPanelManagerToolbarStyles.container.padding,
63
- }, children: toolbar.items.filter(item => item.visible !== false).map((item, index) => {
64
- return _jsxs("div", { children: [item.beginGroup && index !== 0 && (_jsx("hr", { style: { margin: '10px 0', borderTop: '1px solid #c6c6c6', width: '100%' } })), _jsx("button", { disabled: item.disabled ?? false, style: {
65
- display: 'flex',
66
- justifyContent: 'center',
67
- alignItems: 'center',
68
- border: 'none',
69
- cursor: 'pointer',
70
- transition: 'background-color 0.3s, transform 0.2s',
71
- // customizable properties
72
- width: toolbar.styles?.items?.width ?? defaultTMPanelManagerToolbarStyles.items.width,
73
- height: toolbar.styles?.items?.height ?? defaultTMPanelManagerToolbarStyles.items.height,
74
- backgroundColor: item.disabled ? '#cccccc'
75
- : hiddenPanelIds.has(item.panelManagerMatrixRowId)
76
- ? toolbar.styles?.items?.inactiveBackgroundColor ?? defaultTMPanelManagerToolbarStyles.items.inactiveBackgroundColor
77
- : toolbar.styles?.items?.activeBackgroundColor ?? defaultTMPanelManagerToolbarStyles.items.activeBackgroundColor,
78
- borderRadius: toolbar.styles?.items?.borderRadius ?? defaultTMPanelManagerToolbarStyles.items.borderRadius,
79
- marginBottom: toolbar.styles?.items?.marginBottom ?? defaultTMPanelManagerToolbarStyles.items.marginBottom,
80
- }, onMouseEnter: (e) => {
81
- e.currentTarget.style.transform = 'scale(1.1)';
82
- }, onMouseLeave: (e) => {
83
- e.currentTarget.style.transform = 'scale(1)';
84
- }, onClick: () => togglePanelVisibility(item), children: _jsx(TMTooltip, { content: item.tooltipName, children: typeof item.icon === 'string' ? _jsx("i", { className: `dx-icon dx-icon-${item.icon}`, style: { fontSize: toolbar.styles?.items?.iconFontSize ?? defaultTMPanelManagerToolbarStyles.items.iconFontSize, color: toolbar.styles?.items?.iconColor ?? defaultTMPanelManagerToolbarStyles.items.iconColor } }) : item.icon }) })] }, "TMPanelManagerMatrix-Desktop-Toolbar-Item-" + item.id);
85
- }) }, "TMPanelManagerMatrix-Desktop-Toolbar");
86
- };
87
- export const MobileToolbar = (toolbar, hiddenPanelIds, togglePanelVisibility) => {
88
- return _jsx("div", { style: {
89
- width: '100%',
90
- height: '60px',
91
- background: 'linear-gradient(135deg, #e0eafc, #cfdef3)',
92
- display: 'flex',
93
- flexDirection: 'column',
94
- justifyContent: 'center',
95
- borderRadius: '12px',
96
- boxShadow: '0 6px 10px rgba(0, 0, 0, 0.15)',
97
- alignItems: 'center',
98
- padding: '5px',
99
- }, children: _jsx("div", { style: {
100
- display: "flex",
101
- overflow: "hidden",
102
- borderRadius: "12px",
103
- backgroundColor: "#ffffff",
104
- boxShadow: "0 6px 12px rgba(0,0,0,0.1)",
105
- width: "100%",
106
- height: '100%',
107
- }, children: _jsx(ScrollView, { direction: "horizontal", showScrollbar: "always", useNative: Number(SDKUI_Globals.userSettings?.themeSettings.gridSettings.useNativeScrollbar) === 1, style: { whiteSpace: "nowrap", width: "100%" }, children: _jsx("div", { style: { display: "flex", gap: "8px", justifyContent: "center", padding: "5px" }, children: toolbar.items.filter(item => item.visible !== false).map((item, index) => {
108
- return _jsxs("div", { style: { display: "flex", flexDirection: "row", alignItems: "center" }, children: [item.beginGroup && index !== 0 && (_jsx("div", { style: { height: '35px', width: '1px', backgroundColor: '#c6c6c6', margin: '0 8px' } })), _jsx("button", { disabled: item.disabled ?? false, style: {
109
- display: 'flex',
110
- justifyContent: 'center',
111
- alignItems: 'center',
112
- border: 'none',
113
- cursor: 'pointer',
114
- transition: 'background-color 0.3s, transform 0.2s',
115
- // customizable properties
116
- width: toolbar.styles?.items?.width ?? defaultTMPanelManagerToolbarStyles.items.width,
117
- height: toolbar.styles?.items?.height ?? defaultTMPanelManagerToolbarStyles.items.height,
118
- backgroundColor: item.disabled ? '#cccccc'
119
- : hiddenPanelIds.has(item.panelManagerMatrixRowId)
120
- ? toolbar.styles?.items?.inactiveBackgroundColor ?? defaultTMPanelManagerToolbarStyles.items.inactiveBackgroundColor
121
- : toolbar.styles?.items?.activeBackgroundColor ?? defaultTMPanelManagerToolbarStyles.items.activeBackgroundColor,
122
- borderRadius: toolbar.styles?.items?.borderRadius ?? defaultTMPanelManagerToolbarStyles.items.borderRadius,
123
- marginBottom: toolbar.styles?.items?.marginBottom ?? defaultTMPanelManagerToolbarStyles.items.marginBottom,
124
- }, onMouseEnter: (e) => {
125
- e.currentTarget.style.transform = 'scale(1.1)';
126
- }, onMouseLeave: (e) => {
127
- e.currentTarget.style.transform = 'scale(1)';
128
- }, onClick: () => togglePanelVisibility(item), children: _jsx(TMTooltip, { content: item.tooltipName, children: typeof item.icon === 'string' ? _jsx("i", { className: `dx-icon dx-icon-${item.icon}`, style: { fontSize: toolbar.styles?.items?.iconFontSize ?? defaultTMPanelManagerToolbarStyles.items.iconFontSize, color: toolbar.styles?.items?.iconColor ?? defaultTMPanelManagerToolbarStyles.items.iconColor } }) : item.icon }) })] }, "TMPanelManagerMatrix-Mobile-Toolbar-Item-" + item.id);
129
- }) }) }) }) }, "TMPanelManagerMatrix-Mobile-Toolbar");
130
- };
131
- export const getDynamicColumnWidth = (col, hiddenPanelIds, allColumns) => {
132
- if (!col)
133
- return '100%';
134
- // If all rows in the column are hidden, return width 0%
135
- const allRowsHidden = col.rows.every(row => hiddenPanelIds.has(row.id));
136
- if (allRowsHidden)
137
- return '100%';
138
- // Calculate which columns are visible (at least one row not hidden)
139
- const visibleColumns = allColumns.filter(c => c.rows.some(r => !hiddenPanelIds.has(r.id)));
140
- const visibleCount = visibleColumns.length;
141
- // If all columns are visible, use the original widths
142
- if (visibleCount === allColumns.length) {
143
- return col.width ?? `${100 / visibleCount}%`;
144
- }
145
- // If some columns are hidden, redistribute the 100% width among visible columns
146
- // proportionally to their original widths
147
- // First, sum the original widths (in percent) of the visible columns
148
- const totalPercent = visibleColumns.reduce((sum, c) => {
149
- const w = c.width ? parseFloat(c.width) : (100 / visibleCount);
150
- return sum + w;
151
- }, 0);
152
- // Calculate the width percentage for this column based on its original width proportion
153
- const thisPercent = col.width ? parseFloat(col.width) : (100 / visibleCount);
154
- return `${(thisPercent / totalPercent) * 100}%`;
155
- };