@topconsultnpm/sdkui-react-beta 6.13.49 → 6.13.51

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,212 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import React, { useRef, useMemo } from 'react';
3
+ import styled from 'styled-components';
4
+ import { useTMPanelManagerContext } from './TMPanelManagerContext';
5
+ import TMPanelWrapper from './TMPanelWrapper';
6
+ import TMPanelManagerToolbar from './TMPanelManagerToolbar';
7
+ import { IconInfo, SDKUI_Globals, SDKUI_Localizator } from '../../../helper';
8
+ import { DeviceType, useDeviceType } from '../../base/TMDeviceProvider';
9
+ // Styled Component per il contenitore principale di PanelContainer
10
+ const StyledPanelContainerWrapper = styled.div `
11
+ display: ${props => (props.$hasVisiblePanels ? 'flex' : 'none')};
12
+ flex-direction: ${props => (props.$direction === 'vertical' ? 'column' : 'row')};
13
+ height: 100%;
14
+ width: 100%;
15
+ // overflow: hidden;
16
+ `;
17
+ // Styled Component per il gutter (separatore)
18
+ const StyledResizerGutter = styled.div `
19
+ cursor: ${props => (props.$direction === 'horizontal' ? 'col-resize' : 'row-resize')};
20
+ ${props => (props.$direction === 'horizontal' ? `width: ${SDKUI_Globals.userSettings.themeSettings.gutters}px;` : `height: ${SDKUI_Globals.userSettings.themeSettings.gutters}px;`)}
21
+ flex-shrink: 0;
22
+ background-color: "transparent";
23
+ z-index: 10;
24
+ `;
25
+ const TMPanelManagerContainer = (props) => {
26
+ const { panels, showToolbar, direction, parentId } = props;
27
+ const { panelVisibility, setPanelDimensionsById, hasVisiblePanels, maximizedPanels, updateIsResizingActive } = useTMPanelManagerContext();
28
+ // Get the current device type (e.g., mobile, tablet, desktop) using a custom hook
29
+ const deviceType = useDeviceType();
30
+ // This avoids unnecessary re-renders by only recalculating when deviceType changes
31
+ let isMobile = useMemo(() => { return deviceType === DeviceType.MOBILE; }, [deviceType]);
32
+ const gutters = SDKUI_Globals.userSettings.themeSettings.gutters;
33
+ // References to store information related to resizing
34
+ // startMousePos: stores the mouse position when resizing starts (x or y coordinate)
35
+ const startMousePos = useRef(0);
36
+ // initialPanelSizesOnDragStart: stores width/height percentages of panels at drag start
37
+ const initialPanelSizesOnDragStart = useRef({});
38
+ // resizingPanelId: ID of the panel currently being resized
39
+ const resizingPanelId = useRef(null);
40
+ // nextPanelId: ID of the adjacent panel that is resized in tandem
41
+ const nextPanelId = useRef(null);
42
+ // Variable to track if a visible panel has been encountered yet during rendering
43
+ let hasSeenVisiblePanel = false;
44
+ // Ref to the container DOM element that holds all the panels; used for size calculations
45
+ const containerRef = useRef(null);
46
+ // Count how many child panels are currently visible (based on panelVisibility)
47
+ const currentlyVisibleChildrenCount = panels.filter(panel => panelVisibility[panel.id]).length;
48
+ // If this component has a parent panel, and the parent panel is visible,
49
+ // but none of the current child panels are visible, return null to avoid rendering empty container
50
+ if (parentId && panelVisibility[parentId] && currentlyVisibleChildrenCount === 0) {
51
+ return null;
52
+ }
53
+ // Handles mouse movement during panel resizing
54
+ const onMouseMove = (e) => {
55
+ e.preventDefault();
56
+ // Get the IDs of the panels being resized and the initial sizes saved at drag start
57
+ const currentId = resizingPanelId.current;
58
+ const nextId = nextPanelId.current;
59
+ const initialSizes = initialPanelSizesOnDragStart.current;
60
+ // If any info is missing, exit early
61
+ if (!currentId || !nextId || !initialSizes[currentId] || !initialSizes[nextId])
62
+ return;
63
+ // Get the container size (width or height depending on direction)
64
+ const containerSize = direction === 'horizontal' ? containerRef.current?.offsetWidth || 1 : containerRef.current?.offsetHeight || 1;
65
+ // Calculate how much the mouse moved in pixels and as a percentage relative to container size
66
+ const deltaPx = (direction === 'horizontal' ? e.clientX : e.clientY) - startMousePos.current;
67
+ const deltaPercent = (deltaPx / containerSize) * 100;
68
+ // Parse initial panel sizes (width or height) in percentage
69
+ const initialCurrent = parseFloat(direction === 'horizontal' ? initialSizes[currentId].width : initialSizes[currentId].height);
70
+ const initialNext = parseFloat(direction === 'horizontal' ? initialSizes[nextId].width : initialSizes[nextId].height);
71
+ // Calculate new sizes by adding/subtracting deltaPercent
72
+ let newCurrent = initialCurrent + deltaPercent;
73
+ let newNext = initialNext - deltaPercent;
74
+ // Set a minimum size threshold (to prevent panels from collapsing)
75
+ const minPercent = (50 / containerSize) * 100;
76
+ // Correct sizes if they go below the minimum
77
+ if (newCurrent < minPercent) {
78
+ newCurrent = minPercent;
79
+ newNext = initialCurrent + initialNext - minPercent;
80
+ }
81
+ else if (newNext < minPercent) {
82
+ newNext = minPercent;
83
+ newCurrent = initialCurrent + initialNext - minPercent;
84
+ }
85
+ // Update panel sizes (width or height depending on direction)
86
+ if (direction === 'horizontal') {
87
+ setPanelDimensionsById(currentId, `${newCurrent}%`, `100%`);
88
+ setPanelDimensionsById(nextId, `${newNext}%`, `100%`);
89
+ }
90
+ else {
91
+ setPanelDimensionsById(currentId, `100%`, `${newCurrent}%`);
92
+ setPanelDimensionsById(nextId, `100%`, `${newNext}%`);
93
+ }
94
+ };
95
+ // Handles mouse release (end of resizing)
96
+ const onMouseUp = (e) => {
97
+ e.preventDefault();
98
+ // Disable resizing state
99
+ updateIsResizingActive(false);
100
+ // Reset the refs controlling resizing
101
+ resizingPanelId.current = null;
102
+ nextPanelId.current = null;
103
+ initialPanelSizesOnDragStart.current = {};
104
+ // Remove the event listeners added during resizing
105
+ window.removeEventListener('mousemove', onMouseMove);
106
+ window.removeEventListener('mouseup', onMouseUp);
107
+ // Reset cursor and text selection styles to defaults
108
+ document.body.style.cursor = '';
109
+ document.body.style.userSelect = '';
110
+ };
111
+ // Starts the resizing process when mouse is pressed on the gutter
112
+ const startResizing = (e, currentId, nextId) => {
113
+ e.preventDefault();
114
+ const container = containerRef.current;
115
+ if (!container)
116
+ return;
117
+ // Find the two panel elements in the DOM by their data-panel-id attributes
118
+ const currentEl = container.querySelector(`[data-panel-id="${currentId}"]`);
119
+ const nextEl = container.querySelector(`[data-panel-id="${nextId}"]`);
120
+ if (!currentEl || !nextEl)
121
+ return;
122
+ // Set the refs for the panels currently resizing
123
+ resizingPanelId.current = currentId;
124
+ nextPanelId.current = nextId;
125
+ // Save the initial mouse position (X or Y based on direction)
126
+ startMousePos.current = direction === 'horizontal' ? e.clientX : e.clientY;
127
+ // Enable resizing state (for UI feedback if needed)
128
+ updateIsResizingActive(true);
129
+ // Calculate and save the initial panel sizes as percentages of container size
130
+ const containerWidth = container.offsetWidth;
131
+ const containerHeight = container.offsetHeight;
132
+ initialPanelSizesOnDragStart.current = {
133
+ [currentId]: {
134
+ width: `${(currentEl.offsetWidth / containerWidth) * 100}%`,
135
+ height: `${(currentEl.offsetHeight / containerHeight) * 100}%`,
136
+ },
137
+ [nextId]: {
138
+ width: `${(nextEl.offsetWidth / containerWidth) * 100}%`,
139
+ height: `${(nextEl.offsetHeight / containerHeight) * 100}%`,
140
+ },
141
+ };
142
+ // Attach global mousemove and mouseup listeners to track dragging anywhere on screen
143
+ window.addEventListener('mousemove', onMouseMove);
144
+ window.addEventListener('mouseup', onMouseUp);
145
+ // Change the cursor to indicate resizing
146
+ document.body.style.cursor = direction === 'horizontal' ? 'col-resize' : 'row-resize';
147
+ document.body.style.userSelect = 'none';
148
+ };
149
+ return (_jsxs("div", { style: { display: 'flex', height: '100%', width: '100%', flexDirection: isMobile ? 'column' : 'row', gap: gutters }, children: [_jsx("div", { style: {
150
+ display: 'flex',
151
+ width: (isMobile || maximizedPanels.length > 0) ? '100%' : showToolbar ? `calc(100% - 70px)` : '100%',
152
+ height: isMobile ? (showToolbar ? `calc(100% - 55px)` : '100%') : '100%',
153
+ flexDirection: 'row'
154
+ }, children: _jsxs("div", { style: { width: "100%", height: "100%" }, children: [_jsx(StyledPanelContainerWrapper, { ref: containerRef, "$direction": direction, "$hasVisiblePanels": hasVisiblePanels(), children: panels.map((panel, index) => {
155
+ // Check if current panel is visible according to context state
156
+ const isVisible = panelVisibility[panel.id];
157
+ // Find the closest previous visible panel's ID to place the resizer gutter between
158
+ let prevPanelIdForGutter = null;
159
+ for (let i = index - 1; i >= 0; i--) {
160
+ if (panelVisibility[panels[i].id]) {
161
+ prevPanelIdForGutter = panels[i].id;
162
+ break;
163
+ }
164
+ }
165
+ /*
166
+ Determine if we should show the gutter (resize handle) before this panel.
167
+ The gutter is shown only if:
168
+ - this panel is visible
169
+ - at least one visible panel was encountered before this one
170
+ */
171
+ const showGutter = isVisible && hasSeenVisiblePanel && maximizedPanels.length === 0;
172
+ // Update the flag once a visible panel is found, so gutters after it are shown correctly
173
+ if (isVisible) {
174
+ hasSeenVisiblePanel = true;
175
+ }
176
+ // If the panel has child panels, render a nested TMPanelManagerContainer or a component
177
+ if (panel.children && panel.children.length > 0) {
178
+ const ComponentToRender = panel.contentOptions?.component;
179
+ return (_jsxs(React.Fragment, { children: [showGutter && (_jsx(StyledResizerGutter, { "$direction": direction, onMouseDown: (e) => startResizing(e, prevPanelIdForGutter, panel.id) })), _jsx(TMPanelWrapper, { panel: panel, children: ComponentToRender
180
+ ? _jsx(ComponentToRender, { groupId: panel.id, mainDirection: panel.childrenGroupDirection || direction })
181
+ : _jsx(TMPanelManagerContainer, { panels: panel.children, direction: panel.childrenGroupDirection || direction, parentId: panel.id, showToolbar: false }) })] }, panel.id));
182
+ }
183
+ // If the panel has a component specified, render it with a possible gutter before it
184
+ if (panel.contentOptions && panel.contentOptions.component) {
185
+ return (_jsxs(React.Fragment, { children: [showGutter && (_jsx(StyledResizerGutter, { "$direction": direction, onMouseDown: (e) => startResizing(e, prevPanelIdForGutter, panel.id) })), _jsx(TMPanelWrapper, { panel: panel, children: _jsx(panel.contentOptions.component, {}) })] }, panel.id));
186
+ }
187
+ // If no children or components, render nothing for this panel
188
+ return null;
189
+ }) }), _jsxs("div", { style: {
190
+ display: hasVisiblePanels() ? 'none' : 'flex',
191
+ flexDirection: 'column',
192
+ width: '100%',
193
+ height: '100%',
194
+ justifyContent: 'center',
195
+ alignItems: 'center',
196
+ fontSize: '1.5rem',
197
+ color: 'rgb(37, 89, 165)',
198
+ backgroundColor: '#fff',
199
+ borderRadius: '10px'
200
+ }, children: [_jsx(IconInfo, { style: { fontSize: 50 } }), _jsx("div", { children: SDKUI_Localizator.NoPanelSelected })] })] }) }), (showToolbar && maximizedPanels.length === 0) && _jsx("div", { style: {
201
+ display: 'flex',
202
+ flexDirection: isMobile ? 'row' : 'column',
203
+ alignItems: 'center',
204
+ width: isMobile ? '100%' : '50px',
205
+ height: isMobile ? '50px' : 'max-content',
206
+ background: 'transparent linear-gradient(90deg, #CCE0F4 0%, #7EC1E7 14%, #39A6DB 28%, #1E9CD7 35%, #0075BE 78%, #005B97 99%) 0% 0% no-repeat padding-box',
207
+ borderRadius: isMobile ? '10px' : '10px 0px 0px 10px',
208
+ padding: '10px',
209
+ gap: '10px'
210
+ }, children: _jsx(TMPanelManagerToolbar, { panels: panels }) })] }));
211
+ };
212
+ export default TMPanelManagerContainer;
@@ -0,0 +1,36 @@
1
+ import { ReactNode } from 'react';
2
+ import type { TMPanelDefinition, TMPanelDimensionsMap } from './types';
3
+ interface TMPanelManagerContextType {
4
+ panelVisibility: {
5
+ [id: string]: boolean;
6
+ };
7
+ panelDimensions: TMPanelDimensionsMap;
8
+ togglePanelVisibility: (id: string) => void;
9
+ setPanelVisibilityById: (id: string, isVisible: boolean) => void;
10
+ setPanelDimensionsById: (id: string, width: string, height: string) => void;
11
+ hasVisiblePanels: () => boolean;
12
+ maximizedPanels: Array<string>;
13
+ toggleMaximize: (id: string) => void;
14
+ toolbarButtonsVisibility: {
15
+ [id: string]: boolean;
16
+ };
17
+ toolbarButtonsDisabled: {
18
+ [id: string]: boolean;
19
+ };
20
+ setToolbarButtonVisibility: (id: string, isVisible: boolean) => void;
21
+ setToolbarButtonDisabled: (id: string, isDisabled: boolean) => void;
22
+ isResizingActive: boolean;
23
+ updateIsResizingActive: (isActive: boolean) => void;
24
+ }
25
+ interface TMPanelManagerProviderProps {
26
+ children: ReactNode;
27
+ panels: Array<TMPanelDefinition>;
28
+ initialVisibility: {
29
+ [id: string]: boolean;
30
+ };
31
+ initialDimensions: TMPanelDimensionsMap;
32
+ initialMobilePanelId: string;
33
+ }
34
+ export declare const TMPanelManagerProvider: (props: TMPanelManagerProviderProps) => import("react/jsx-runtime").JSX.Element;
35
+ export declare const useTMPanelManagerContext: () => TMPanelManagerContextType;
36
+ export {};
@@ -0,0 +1,234 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { createContext, useContext, useState, useCallback, useMemo, useEffect, useRef } from 'react';
3
+ import { generatePanelHierarchyMap, hideParentRecursively, showParentRecursively, redistributeDimensionsOnHide, redistributeDimensionsOnShow, getToolbarStates } from './utils';
4
+ import { DeviceType, useDeviceType } from '../../base/TMDeviceProvider';
5
+ // Create a React context for the panel state and actions
6
+ const TMPanelManagerContext = createContext(undefined);
7
+ export const TMPanelManagerProvider = (props) => {
8
+ const { children, panels, initialDimensions, initialVisibility, initialMobilePanelId } = props;
9
+ // Get the current device type (e.g., mobile, tablet, desktop) using a custom hook
10
+ const deviceType = useDeviceType();
11
+ // This avoids unnecessary re-renders by only recalculating when deviceType changes
12
+ let isMobile = useMemo(() => { return deviceType === DeviceType.MOBILE; }, [deviceType]);
13
+ // Ref to persist the initial panel dimensions across renders without causing re-renders
14
+ const initialPanelDimensionsRef = useRef(initialDimensions);
15
+ // Memoize the panel hierarchy map to avoid re-generating it on every render unless `panels` change
16
+ const hierarchyMap = useMemo(() => generatePanelHierarchyMap(panels), [panels]);
17
+ // State to track the dimensions (width and height) of each panel, initialized with props
18
+ const [panelDimensions, setPanelDimensions] = useState(initialDimensions);
19
+ // IDs of maximized panels, including their parents if present
20
+ const [maximizedPanels, setMaximizedPanels] = useState([]);
21
+ // State to track the visibility of each panel, keyed by panel ID
22
+ const [panelVisibility, setPanelVisibility] = useState({});
23
+ // State to track visibility of toolbar buttons, keyed by panel ID
24
+ const [toolbarButtonsVisibility, setToolbarButtonsVisibility] = useState({});
25
+ // State to track disabled status of toolbar buttons, keyed by panel ID
26
+ const [toolbarButtonsDisabled, setToolbarButtonsDisabled] = useState({});
27
+ // State to track if a resizing operation is currently active (mouse drag on gutter)
28
+ const [isResizingActive, setIsResizingActive] = useState(false);
29
+ useEffect(() => {
30
+ const { visibilityMap, disabledMap } = getToolbarStates(panels);
31
+ setToolbarButtonsVisibility(visibilityMap);
32
+ setToolbarButtonsDisabled(disabledMap);
33
+ }, [panels]);
34
+ // Callback to update the visibility state of a specific panel and its related hierarchy
35
+ const updatePanelVisibility = useCallback((id, isVisible, prevVisibility) => {
36
+ // Clone previous visibility state to work with
37
+ let updated = { ...prevVisibility };
38
+ if (isMobile) {
39
+ if (isVisible) {
40
+ // On mobile, showing one panel hides all others first
41
+ updated = Object.keys(prevVisibility).reduce((acc, key) => { acc[key] = false; return acc; }, {});
42
+ // Recursively determine and show parent panels
43
+ const parentsToShow = showParentRecursively(id, hierarchyMap);
44
+ parentsToShow.forEach(([pid, visible]) => { updated[pid] = visible; });
45
+ // Show the target panel
46
+ updated[id] = true;
47
+ // Redistribute dimensions to account for newly shown panel
48
+ setPanelDimensions(prev => redistributeDimensionsOnShow(id, prev, initialPanelDimensionsRef.current, hierarchyMap, updated));
49
+ }
50
+ else {
51
+ // Hide the panel
52
+ updated[id] = false;
53
+ // Recursively hide parents
54
+ const parentsToHide = hideParentRecursively(id, hierarchyMap, updated);
55
+ parentsToHide.forEach(([pid, visible]) => { updated[pid] = visible; });
56
+ 4;
57
+ // Redistribute dimensions after hiding
58
+ setPanelDimensions(prev => redistributeDimensionsOnHide(id, prev, hierarchyMap, updated));
59
+ }
60
+ }
61
+ else {
62
+ if (isVisible) {
63
+ // On desktop, show panel and its parents without hiding others
64
+ const parentsToShow = showParentRecursively(id, hierarchyMap);
65
+ parentsToShow.forEach(([pid, visible]) => { updated[pid] = visible; });
66
+ // Show the panel
67
+ updated[id] = true;
68
+ // Adjust panel dimensions accordingly
69
+ setPanelDimensions(prev => redistributeDimensionsOnShow(id, prev, initialPanelDimensionsRef.current, hierarchyMap, updated));
70
+ }
71
+ else {
72
+ // Hide the panel
73
+ updated[id] = false;
74
+ // Recursively hide parent panels
75
+ const parentsToHide = hideParentRecursively(id, hierarchyMap, updated);
76
+ parentsToHide.forEach(([pid, visible]) => { updated[pid] = visible; });
77
+ // Adjust dimensions after hiding
78
+ setPanelDimensions(prev => redistributeDimensionsOnHide(id, prev, hierarchyMap, updated));
79
+ }
80
+ }
81
+ return updated;
82
+ }, [hierarchyMap, isMobile]);
83
+ // On initial mount: initialize panel visibility using the provided `initialVisibility` config
84
+ // Each panel's visibility is processed through `updatePanelVisibility` to ensure hierarchy logic is applied
85
+ useEffect(() => {
86
+ let updated = { ...initialVisibility };
87
+ Object.entries(initialVisibility).forEach(([id, isVisible]) => {
88
+ updated = updatePanelVisibility(id, isVisible, updated);
89
+ });
90
+ setPanelVisibility(updated);
91
+ }, []);
92
+ // On mobile devices: automatically show the initial mobile panel when layout switches to mobile
93
+ // This ensures the correct panel is visible by default on smaller screens
94
+ useEffect(() => {
95
+ if (isMobile) {
96
+ setPanelVisibility(prev => {
97
+ return updatePanelVisibility(initialMobilePanelId, true, prev);
98
+ });
99
+ }
100
+ }, [isMobile, updatePanelVisibility]);
101
+ // Function to maximize a panel (and its parent panels)
102
+ const maximizePanel = useCallback((id) => {
103
+ // Get all parent panel IDs recursively for the given panel ID
104
+ const parents = showParentRecursively(id, hierarchyMap).map(([pid]) => pid);
105
+ 4;
106
+ // Combine parents and the current panel ID into one array to maximize
107
+ const toMaximize = [...parents, id];
108
+ // Update state to track which panels are maximized
109
+ setMaximizedPanels(toMaximize);
110
+ // Update panel dimensions accordingly
111
+ setPanelDimensions(prev => {
112
+ const newDimensions = {};
113
+ Object.keys(prev).forEach(pid => {
114
+ if (toMaximize.includes(pid)) {
115
+ // For maximized panels (target + parents), set width and height to 100%
116
+ newDimensions[pid] = { width: '100%', height: '100%' };
117
+ }
118
+ else {
119
+ // For all other panels, collapse by setting dimensions to 0%
120
+ newDimensions[pid] = { width: '0%', height: '0%' };
121
+ }
122
+ });
123
+ return newDimensions;
124
+ });
125
+ }, [hierarchyMap]);
126
+ // Restore all panels to their original dimensions
127
+ const resetMaximization = useCallback(() => {
128
+ // Clear the list of maximized panels to exit maximize mode
129
+ setMaximizedPanels([]);
130
+ // Clone the current visibility state of panels
131
+ let updatedVisibility = { ...panelVisibility };
132
+ // Start with the initial panel dimensions as the base for restoration
133
+ let nextDimensions = { ...initialPanelDimensionsRef.current };
134
+ nextDimensions = Object.entries(updatedVisibility).reduce((acc, [id, isVisible]) => {
135
+ if (isVisible) {
136
+ // If the panel is visible, adjust dimensions to show it properly
137
+ acc = redistributeDimensionsOnShow(id, acc, initialPanelDimensionsRef.current, hierarchyMap, updatedVisibility);
138
+ }
139
+ else {
140
+ // If the panel is hidden, adjust dimensions to hide it
141
+ acc = redistributeDimensionsOnHide(id, acc, hierarchyMap, updatedVisibility);
142
+ }
143
+ return acc;
144
+ }, initialPanelDimensionsRef.current);
145
+ // Apply the recalculated dimensions to the panel state
146
+ setPanelDimensions(nextDimensions);
147
+ }, [initialVisibility, panelVisibility, hierarchyMap]);
148
+ // Toggle the maximized state of a panel by its ID
149
+ const toggleMaximize = useCallback((id) => {
150
+ if (maximizedPanels.includes(id)) {
151
+ // If already maximized, restore
152
+ resetMaximization();
153
+ }
154
+ else {
155
+ // Otherwise, maximize the selected panel and its parents
156
+ maximizePanel(id);
157
+ }
158
+ }, [maximizedPanels, maximizePanel, resetMaximization]);
159
+ // Toggles the visibility of a panel by its I
160
+ const togglePanelVisibility = useCallback((id) => {
161
+ // Check if the panel to toggle is currently maximized
162
+ if (maximizedPanels.length > 0) {
163
+ // If it is maximized, first reset all maximized panels to their original state
164
+ resetMaximization();
165
+ // Then update the visibility of the panel, toggling its current state
166
+ setPanelVisibility(prev => {
167
+ const isCurrentlyVisible = prev[id];
168
+ return updatePanelVisibility(id, !isCurrentlyVisible, prev);
169
+ });
170
+ }
171
+ else {
172
+ // If the panel is not maximized, simply toggle its visibility
173
+ setPanelVisibility(prev => {
174
+ const isCurrentlyVisible = prev[id];
175
+ return updatePanelVisibility(id, !isCurrentlyVisible, prev);
176
+ });
177
+ }
178
+ }, [maximizedPanels, resetMaximization, updatePanelVisibility]);
179
+ // Sets the visibility of a panel by its ID to a specific value (true = show, false = hide)
180
+ const setPanelVisibilityById = useCallback((id, isVisible) => {
181
+ // If we are trying to hide a panel that is currently maximized, first reset the maximization to restore normal view before hiding
182
+ if (maximizedPanels.length > 0) {
183
+ resetMaximization();
184
+ }
185
+ // Then update the visibility state of the panel to the given value
186
+ setPanelVisibility(prev => updatePanelVisibility(id, isVisible, prev));
187
+ }, [maximizedPanels, resetMaximization, updatePanelVisibility]);
188
+ // Sets the dimensions (width and height) of a specific panel by its ID
189
+ const setPanelDimensionsById = useCallback((id, width, height) => {
190
+ // Update the ref holding the initial dimensions
191
+ initialPanelDimensionsRef.current = { ...initialPanelDimensionsRef.current, [id]: { width, height } };
192
+ // Update the panel dimensions state
193
+ setPanelDimensions(prev => ({ ...prev, [id]: { width, height } }));
194
+ }, []);
195
+ // Checks if there is at least one panel currently visible
196
+ const hasVisiblePanels = useCallback(() => {
197
+ // Returns true if any value in the visibility map is `true`
198
+ return Object.values(panelVisibility).some(isVisible => isVisible);
199
+ }, [panelVisibility]);
200
+ // Callback to update the visibility of a toolbar button by panel ID
201
+ const setToolbarButtonVisibility = useCallback((id, isVisible) => {
202
+ setToolbarButtonsVisibility(prev => ({ ...prev, [id]: isVisible }));
203
+ }, []);
204
+ // Callback to update the disabled state of a toolbar button by panel ID
205
+ const setToolbarButtonDisabled = useCallback((id, isDisabled) => {
206
+ setToolbarButtonsDisabled(prev => ({ ...prev, [id]: isDisabled }));
207
+ }, []);
208
+ const updateIsResizingActive = useCallback((isActive) => {
209
+ setIsResizingActive(isActive);
210
+ }, []);
211
+ return (_jsx(TMPanelManagerContext.Provider, { value: {
212
+ panelVisibility,
213
+ panelDimensions,
214
+ togglePanelVisibility,
215
+ setPanelVisibilityById,
216
+ setPanelDimensionsById,
217
+ hasVisiblePanels,
218
+ maximizedPanels,
219
+ toggleMaximize,
220
+ toolbarButtonsVisibility,
221
+ toolbarButtonsDisabled,
222
+ setToolbarButtonVisibility,
223
+ setToolbarButtonDisabled,
224
+ isResizingActive,
225
+ updateIsResizingActive
226
+ }, children: children }));
227
+ };
228
+ export const useTMPanelManagerContext = () => {
229
+ const context = useContext(TMPanelManagerContext);
230
+ if (context === undefined) {
231
+ throw new Error('useTMPanelContext must be used within a TMPanelProvider');
232
+ }
233
+ return context;
234
+ };
@@ -0,0 +1,11 @@
1
+ import React from 'react';
2
+ import { TMPanelDefinition } from './types';
3
+ export declare const StyledToolbarButton: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components/dist/types").Substitute<React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>, {
4
+ $isActive?: boolean;
5
+ $isDisabled?: boolean;
6
+ }>> & string;
7
+ interface TMPanelManagerToolbarProps {
8
+ panels: Array<TMPanelDefinition>;
9
+ }
10
+ declare const TMPanelManagerToolbar: (props: TMPanelManagerToolbarProps) => import("react/jsx-runtime").JSX.Element;
11
+ export default TMPanelManagerToolbar;
@@ -0,0 +1,58 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useEffect, useMemo, useState } from 'react';
3
+ import styled from 'styled-components';
4
+ import { useTMPanelManagerContext } from './TMPanelManagerContext';
5
+ import { DeviceType, useDeviceType } from '../../base/TMDeviceProvider';
6
+ import { flattenPanels } from './utils';
7
+ import TMTooltip from '../../base/TMTooltip';
8
+ export const StyledToolbarButton = styled.button `
9
+ display: flex;
10
+ align-items: center;
11
+ justify-content: center;
12
+ height: 32px;
13
+ width: 32px;
14
+ border: none;
15
+ border-radius: 8px;
16
+ font-size: 18px;
17
+ padding: 0px;
18
+ color: #fff;
19
+ transition: transform 0.2s ease, box-shadow 0.2s ease;
20
+ cursor: ${({ $isDisabled }) => ($isDisabled ? 'not-allowed' : 'pointer')};
21
+ opacity: ${({ $isDisabled }) => ($isDisabled ? 0.6 : 1)};
22
+ background: ${({ $isActive }) => $isActive ? 'rgba(255,255,255,0.35)' : 'transparent'};
23
+ &:hover {
24
+ background: ${({ $isDisabled }) => !$isDisabled ? 'rgba(255,255,255,0.35)' : undefined};
25
+ }
26
+ `;
27
+ const TMPanelManagerToolbar = (props) => {
28
+ const { panels } = props;
29
+ // Get panel visibility toggling function and visibility state from context
30
+ const { togglePanelVisibility, panelVisibility, toolbarButtonsDisabled, toolbarButtonsVisibility } = useTMPanelManagerContext();
31
+ // Get the current device type (e.g., mobile, tablet, desktop) using a custom hook
32
+ const deviceType = useDeviceType();
33
+ // This avoids unnecessary re-renders by only recalculating when deviceType changes
34
+ let isMobile = useMemo(() => { return deviceType === DeviceType.MOBILE; }, [deviceType]);
35
+ const [visibleLeafPanels, setVisibleLeafPanels] = useState([]);
36
+ useEffect(() => {
37
+ // Flatten panels, filter visible leaf nodes, and sort by orderNumber
38
+ const visibleLeafPanelsSorted = flattenPanels(panels)
39
+ .filter(panel => panel.toolbarOptions?.visible && !panel.children?.length)
40
+ .sort((a, b) => (a.toolbarOptions?.orderNumber ?? 0) - (b.toolbarOptions?.orderNumber ?? 0));
41
+ // Update state with the filtered and sorted leaf panels for the toolbar
42
+ setVisibleLeafPanels(visibleLeafPanelsSorted);
43
+ }, [panels]);
44
+ return (_jsx("div", { style: {
45
+ display: 'flex',
46
+ flexDirection: isMobile ? 'row' : 'column',
47
+ gap: '6px',
48
+ alignItems: 'center',
49
+ justifyContent: isMobile ? 'center' : 'flex-start',
50
+ width: '100%',
51
+ height: '100%'
52
+ }, children: visibleLeafPanels.filter(panel => toolbarButtonsVisibility[panel.id]).map(visibleLeafPanel => {
53
+ const isActive = panelVisibility[visibleLeafPanel.id];
54
+ const isDisabled = toolbarButtonsDisabled[visibleLeafPanel.id];
55
+ return _jsx(TMTooltip, { content: visibleLeafPanel.name, position: isMobile ? 'top' : 'left', children: _jsx(StyledToolbarButton, { disabled: isDisabled, "$isDisabled": isDisabled, onClick: () => togglePanelVisibility(visibleLeafPanel.id), "$isActive": isActive || visibleLeafPanel.toolbarOptions?.alwaysActiveColor, children: typeof visibleLeafPanel.toolbarOptions?.icon === 'string' ? (_jsx("i", { className: `dx-icon dx-icon-${visibleLeafPanel.toolbarOptions?.icon}` })) : (visibleLeafPanel.toolbarOptions?.icon) }, visibleLeafPanel.id) }, visibleLeafPanel.id);
56
+ }) }));
57
+ };
58
+ export default TMPanelManagerToolbar;
@@ -0,0 +1,8 @@
1
+ import { ReactNode } from 'react';
2
+ import { TMPanelDefinition } from './types';
3
+ interface TMPanelWrapperProps {
4
+ panel: TMPanelDefinition;
5
+ children: ReactNode;
6
+ }
7
+ declare const TMPanelWrapper: (props: TMPanelWrapperProps) => import("react/jsx-runtime").JSX.Element;
8
+ export default TMPanelWrapper;
@@ -0,0 +1,35 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useMemo } from 'react';
3
+ import { useTMPanelManagerContext } from './TMPanelManagerContext';
4
+ import { DeviceType, useDeviceType } from '../../base/TMDeviceProvider';
5
+ import TMPanel from '../../base/TMPanel';
6
+ const TMPanelWrapper = (props) => {
7
+ const { panel, children } = props;
8
+ const { panelVisibility, panelDimensions, togglePanelVisibility, maximizedPanels, toggleMaximize } = useTMPanelManagerContext();
9
+ // Get the current device type (e.g., mobile, tablet, desktop) using a custom hook
10
+ const deviceType = useDeviceType();
11
+ // This avoids unnecessary re-renders by only recalculating when deviceType changes
12
+ let isMobile = useMemo(() => { return deviceType === DeviceType.MOBILE; }, [deviceType]);
13
+ // Extract panel dimensions based on panel id
14
+ const width = panelDimensions[panel.id].width;
15
+ const height = panelDimensions[panel.id].height;
16
+ // Determine visibility:
17
+ // - If any panels are maximized, only show those maximized panels
18
+ // - Otherwise, rely on the normal panel visibility state
19
+ const isVisible = maximizedPanels.length > 0 ? maximizedPanels.includes(panel.id) : panelVisibility[panel.id];
20
+ const panelStyles = {
21
+ margin: '0',
22
+ // overflow: 'hidden',
23
+ boxSizing: 'border-box',
24
+ display: isVisible ? 'flex' : 'none',
25
+ flexDirection: 'column',
26
+ minWidth: '50px',
27
+ minHeight: '50px',
28
+ width: width,
29
+ height: height,
30
+ pointerEvents: 'auto',
31
+ };
32
+ return (_jsx("div", { "data-panel-id": panel.id, style: panelStyles, children: panel.contentOptions?.panelContainer ?
33
+ _jsx(TMPanel, { ...panel.contentOptions.panelContainer, allowMaximize: !isMobile, onHeaderDoubleClick: () => toggleMaximize(panel.id), onMaximize: () => toggleMaximize(panel.id), onClose: () => togglePanelVisibility(panel.id), children: children }) : children }));
34
+ };
35
+ export default TMPanelWrapper;
@@ -0,0 +1,36 @@
1
+ import { ITMPanelProps } from "../../base/TMPanel";
2
+ export type TMPanelDirection = 'horizontal' | 'vertical';
3
+ export interface TMPanelDefinition {
4
+ id: string;
5
+ name: string;
6
+ children?: Array<TMPanelDefinition>;
7
+ currentGroupDirection?: TMPanelDirection;
8
+ childrenGroupDirection?: TMPanelDirection;
9
+ contentOptions?: {
10
+ component: React.ComponentType<any>;
11
+ panelContainer?: ITMPanelProps;
12
+ };
13
+ toolbarOptions?: {
14
+ icon: string | JSX.Element;
15
+ visible: boolean;
16
+ isActive: boolean;
17
+ disabled?: boolean;
18
+ orderNumber?: number;
19
+ beginGroup?: boolean;
20
+ alwaysActiveColor?: boolean;
21
+ };
22
+ }
23
+ export interface TMPanelHierarchyInfo {
24
+ groupDirection: TMPanelDirection;
25
+ parentId: string | undefined;
26
+ childrenIds: Array<string>;
27
+ siblingIds: Array<string>;
28
+ }
29
+ export type TMPanelHierarchyMap = Map<string, TMPanelHierarchyInfo>;
30
+ export type TMPanelDimensions = {
31
+ width: string;
32
+ height: string;
33
+ };
34
+ export type TMPanelDimensionsMap = {
35
+ [id: string]: TMPanelDimensions;
36
+ };
@@ -0,0 +1 @@
1
+ export {};