@topconsultnpm/sdkui-react 6.19.0-dev2.51 → 6.19.0-dev2.53
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/lib/components/base/TMModal.js +37 -3
- package/lib/components/base/TMPopUp.js +4 -1
- package/lib/components/features/assistant/ToppyDraggableHelpCenter.d.ts +2 -0
- package/lib/components/features/assistant/ToppyDraggableHelpCenter.js +120 -51
- package/lib/components/features/assistant/ToppySpeechBubble.d.ts +1 -0
- package/lib/components/features/assistant/ToppySpeechBubble.js +56 -3
- package/lib/components/features/documents/TMDcmtForm.js +2 -2
- package/lib/components/features/search/TMSearchQueryPanel.js +2 -3
- package/lib/components/features/search/TMSearchResult.js +11 -12
- package/lib/components/index.d.ts +0 -1
- package/lib/components/index.js +0 -1
- package/lib/helper/TMToppyMessage.js +2 -1
- package/package.json +1 -1
- package/lib/components/features/assistant/ToppyHelpCenter.d.ts +0 -12
- package/lib/components/features/assistant/ToppyHelpCenter.js +0 -173
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { useState, useEffect } from 'react';
|
|
2
|
+
import { useState, useEffect, useRef } from 'react';
|
|
3
3
|
import { Popup } from 'devextreme-react';
|
|
4
4
|
import styled from 'styled-components';
|
|
5
5
|
import TMLayoutContainer, { TMCard, TMLayoutItem } from './TMLayout';
|
|
@@ -40,11 +40,45 @@ const StyledModalContext = styled.div `
|
|
|
40
40
|
height: 100%;
|
|
41
41
|
`;
|
|
42
42
|
const TMModal = ({ resizable = true, expandable = false, isModal = true, title = '', toolbar, onClose, children, width = '100%', height = '100%', fontSize = FontSize.defaultFontSize, hidePopup = true, askClosingConfirm = false, showCloseButton = true }) => {
|
|
43
|
+
const popupRef = useRef(null);
|
|
43
44
|
const [initialWidth, setInitialWidth] = useState(width);
|
|
44
45
|
const [initialHeight, setInitialHeight] = useState(height);
|
|
45
46
|
const [showPopup, setShowPopup] = useState(false);
|
|
46
47
|
const [isResizing, setIsResizing] = useState(false);
|
|
47
48
|
const [isFullScreen, setIsFullScreen] = useState(false);
|
|
49
|
+
// Dimensioni minime di default per il popup
|
|
50
|
+
const DEFAULT_MIN_WIDTH = 500;
|
|
51
|
+
const DEFAULT_MIN_HEIGHT = 400;
|
|
52
|
+
const [minWidth, setMinWidth] = useState(0);
|
|
53
|
+
const [minHeight, setMinHeight] = useState(0);
|
|
54
|
+
/**
|
|
55
|
+
* Gestore chiamato quando il popup viene mostrato.
|
|
56
|
+
* Calcola e imposta le dimensioni minime del popup in base allo spazio disponibile.
|
|
57
|
+
* Le dimensioni minime sono il valore più piccolo tra le dimensioni effettive del parent e le dimensioni minime di default
|
|
58
|
+
*/
|
|
59
|
+
const handleShown = () => {
|
|
60
|
+
// Usa requestAnimationFrame per attendere il completamento del rendering
|
|
61
|
+
requestAnimationFrame(() => {
|
|
62
|
+
if (popupRef.current && popupRef.current.instance) {
|
|
63
|
+
const instance = popupRef.current.instance();
|
|
64
|
+
if (instance) {
|
|
65
|
+
// Ottiene l'elemento content del popup
|
|
66
|
+
const element = instance.content();
|
|
67
|
+
if (element) {
|
|
68
|
+
const parentElement = element.parentElement;
|
|
69
|
+
if (parentElement) {
|
|
70
|
+
// Legge le dimensioni effettive del contenitore parent
|
|
71
|
+
const widthPx = parentElement.offsetWidth;
|
|
72
|
+
const heightPx = parentElement.offsetHeight;
|
|
73
|
+
// Imposta le dimensioni minime usando il valore più piccolo tra le dimensioni disponibili e quelle di default
|
|
74
|
+
setMinWidth(widthPx < DEFAULT_MIN_WIDTH ? widthPx : DEFAULT_MIN_WIDTH);
|
|
75
|
+
setMinHeight(heightPx < DEFAULT_MIN_HEIGHT ? heightPx : DEFAULT_MIN_HEIGHT);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
};
|
|
48
82
|
useEffect(() => {
|
|
49
83
|
setShowPopup(isModal);
|
|
50
84
|
}, [isModal]);
|
|
@@ -67,7 +101,7 @@ const TMModal = ({ resizable = true, expandable = false, isModal = true, title =
|
|
|
67
101
|
setShowPopup(false);
|
|
68
102
|
onClose && onClose();
|
|
69
103
|
};
|
|
70
|
-
return (_jsx(_Fragment, { children: isModal ? (_jsx(Popup, { showCloseButton: showCloseButton, animation: undefined, maxHeight: '95%', maxWidth: '95%', dragEnabled: !isResizing, resizeEnabled: resizable, width: expandable && isFullScreen ? '95%' : initialWidth, height: expandable && isFullScreen ? '95%' : initialHeight, title: title, visible: showPopup, onResizeStart: handleResizeStart, onResizeEnd: handleResizeEnd, onHiding: onHiding, toolbarItems: expandable ? [
|
|
104
|
+
return (_jsx(_Fragment, { children: isModal ? (_jsx(Popup, { ref: popupRef, showCloseButton: showCloseButton, animation: undefined, minWidth: minWidth, minHeight: minHeight, maxHeight: '95%', maxWidth: '95%', dragEnabled: !isResizing, resizeEnabled: resizable, width: expandable && isFullScreen ? '95%' : initialWidth, height: expandable && isFullScreen ? '95%' : initialHeight, title: title, visible: showPopup, onShown: handleShown, onResizeStart: handleResizeStart, onResizeEnd: handleResizeEnd, onHiding: onHiding, toolbarItems: expandable ? [
|
|
71
105
|
{
|
|
72
106
|
widget: 'dxButton',
|
|
73
107
|
location: 'after',
|
|
@@ -76,6 +110,6 @@ const TMModal = ({ resizable = true, expandable = false, isModal = true, title =
|
|
|
76
110
|
onClick: () => setIsFullScreen(!isFullScreen)
|
|
77
111
|
}
|
|
78
112
|
}
|
|
79
|
-
] : undefined, children: _jsxs(TMLayoutContainer, { children: [toolbar && (_jsx(TMLayoutItem, { height: "40px", children: _jsx(StyledModalToolbar, { children: toolbar }) })), _jsx(TMLayoutItem, { height: toolbar ? 'calc(100% -
|
|
113
|
+
] : undefined, children: _jsxs(TMLayoutContainer, { children: [toolbar && (_jsx(TMLayoutItem, { height: "40px", children: _jsx(StyledModalToolbar, { children: toolbar }) })), _jsx(TMLayoutItem, { height: toolbar ? 'calc(100% - 40px)' : '100%', children: _jsx(TMCard, { showBorder: false, padding: false, scrollY: true, children: children }) })] }) })) : (_jsxs(StyledModal, { "$isModal": isModal, className: "temp-modal", "$fontSize": fontSize, "$width": initialWidth, "$height": initialHeight, children: [toolbar ? _jsx(StyledModalToolbar, { children: toolbar }) : _jsx(_Fragment, {}), _jsx(StyledModalContext, { children: children })] })) }));
|
|
80
114
|
};
|
|
81
115
|
export default TMModal;
|
|
@@ -33,7 +33,8 @@ const StyledExeptionToolbar = styled.div `
|
|
|
33
33
|
background-color: white;
|
|
34
34
|
`;
|
|
35
35
|
const StyledMessageToolbar = styled.div `
|
|
36
|
-
position:
|
|
36
|
+
position: sticky;
|
|
37
|
+
bottom: 0;
|
|
37
38
|
display: flex;
|
|
38
39
|
flex-direction: row;
|
|
39
40
|
justify-content: flex-end;
|
|
@@ -95,6 +96,7 @@ const ResponsiveMessageContainer = styled.div `
|
|
|
95
96
|
height: 100%;
|
|
96
97
|
width: 100%;
|
|
97
98
|
background-color: #ffffff;
|
|
99
|
+
overflow: hidden;
|
|
98
100
|
`;
|
|
99
101
|
const ResponsiveMessageContent = styled.div `
|
|
100
102
|
flex: 1;
|
|
@@ -105,6 +107,7 @@ const ResponsiveMessageContent = styled.div `
|
|
|
105
107
|
gap: clamp(8px, 2vw, 20px);
|
|
106
108
|
padding: clamp(8px, 3vw, 25px) clamp(5px, 2vw, 30px);
|
|
107
109
|
min-height: clamp(80px, 15vh, 120px);
|
|
110
|
+
overflow: auto;
|
|
108
111
|
|
|
109
112
|
@media (min-width: 300px) {
|
|
110
113
|
flex-direction: ${props => props.$isMobile ? 'column' : 'row'};
|
|
@@ -16,6 +16,8 @@ interface ToppyDraggableHelpCenterProps {
|
|
|
16
16
|
onToppyImageClick?: () => void;
|
|
17
17
|
/** Visibilità del componente */
|
|
18
18
|
isVisible?: boolean;
|
|
19
|
+
/** Se true, renderizza Toppy nel document.body tramite Portal invece che nel parent */
|
|
20
|
+
usePortal?: boolean;
|
|
19
21
|
}
|
|
20
22
|
/**
|
|
21
23
|
* Componente ToppyDraggableHelpCenter
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useRef, useState, useEffect } from 'react';
|
|
3
|
+
import ReactDOM from 'react-dom';
|
|
3
4
|
import styled from 'styled-components';
|
|
4
5
|
import Toppy from '../../../assets/Toppy-generico.png';
|
|
5
6
|
import { DeviceType } from '../../base/TMDeviceProvider';
|
|
@@ -25,7 +26,7 @@ const ToppyButton = styled.div.attrs((props) => ({
|
|
|
25
26
|
})) `
|
|
26
27
|
/* Visibilità controllata dalla prop */
|
|
27
28
|
display: ${(props) => (props.$isVisible ? 'flex' : 'none')};
|
|
28
|
-
position: absolute;
|
|
29
|
+
position: ${(props) => props.$usePortal ? 'fixed' : 'absolute'};
|
|
29
30
|
|
|
30
31
|
/* Posizionamento di default quando non è draggato (x e y non sono definiti) */
|
|
31
32
|
${(props) => props.$x === undefined || props.$y === undefined
|
|
@@ -126,7 +127,7 @@ const DragOverlay = styled.div `
|
|
|
126
127
|
* in una speech bubble. Il componente può essere trascinato all'interno del suo
|
|
127
128
|
* contenitore e può essere collassato/espanso con un doppio click.
|
|
128
129
|
*/
|
|
129
|
-
const ToppyDraggableHelpCenter = ({ content, deviceType, align = 'right', onToppyImageClick, initialIsCollapsed, isVisible = true, }) => {
|
|
130
|
+
const ToppyDraggableHelpCenter = ({ content, deviceType, align = 'right', onToppyImageClick, initialIsCollapsed, isVisible = true, usePortal = false, }) => {
|
|
130
131
|
// Ref per il contenitore principale
|
|
131
132
|
const buttonRef = useRef(null);
|
|
132
133
|
// Stato per controllare se il componente è collassato o espanso
|
|
@@ -153,57 +154,112 @@ const ToppyDraggableHelpCenter = ({ content, deviceType, align = 'right', onTopp
|
|
|
153
154
|
}
|
|
154
155
|
}, [content, isCollapsed]);
|
|
155
156
|
/**
|
|
156
|
-
* Effect per
|
|
157
|
-
* Questo previene che il componente finisca fuori dai bordi dopo un resize.
|
|
157
|
+
* Effect per verificare e aggiustare la posizione quando cambia lo stato di collassamento
|
|
158
158
|
*/
|
|
159
159
|
useEffect(() => {
|
|
160
|
-
if (!buttonRef.current)
|
|
160
|
+
if (!buttonRef.current || !position)
|
|
161
161
|
return;
|
|
162
|
-
const
|
|
163
|
-
|
|
162
|
+
const rect = buttonRef.current.getBoundingClientRect();
|
|
163
|
+
// Spazio extra occupato dalla bubble quando non è collassato
|
|
164
|
+
const extraHeight = !isCollapsed ? bubbleSize.height : 0;
|
|
165
|
+
const extraWidth = !isCollapsed ? bubbleSize.width : 0;
|
|
166
|
+
let minX = 0;
|
|
167
|
+
let maxX;
|
|
168
|
+
let maxY;
|
|
169
|
+
const bubbleBuffer = 5;
|
|
170
|
+
if (usePortal) {
|
|
171
|
+
// Calcola i limiti usando le dimensioni del viewport
|
|
172
|
+
maxX = window.innerWidth - rect.width;
|
|
173
|
+
maxY = window.innerHeight - rect.height + 20;
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
// Calcola i limiti usando le dimensioni del parent
|
|
177
|
+
const parent = buttonRef.current.offsetParent;
|
|
178
|
+
if (!parent)
|
|
179
|
+
return;
|
|
180
|
+
const parentRect = parent.getBoundingClientRect();
|
|
181
|
+
maxX = parentRect.width - rect.width;
|
|
182
|
+
maxY = parentRect.height - rect.height + 20;
|
|
183
|
+
}
|
|
184
|
+
if (!isCollapsed) {
|
|
185
|
+
if (align === 'right') {
|
|
186
|
+
minX = Math.max(0, extraWidth - rect.width);
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
maxX = Math.min(maxX, (usePortal ? window.innerWidth : buttonRef.current.offsetParent?.getBoundingClientRect().width || 0) - extraWidth);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
// Verifica se la posizione corrente è fuori dai limiti
|
|
193
|
+
const isOutOfBounds = position.x < minX ||
|
|
194
|
+
position.x > maxX ||
|
|
195
|
+
position.y < extraHeight + bubbleBuffer ||
|
|
196
|
+
position.y > maxY;
|
|
197
|
+
// Se è fuori dai limiti, aggiusta la posizione
|
|
198
|
+
if (isOutOfBounds) {
|
|
199
|
+
const adjustedX = Math.max(minX, Math.min(position.x, maxX));
|
|
200
|
+
const adjustedY = Math.max(extraHeight + bubbleBuffer, Math.min(position.y, maxY));
|
|
201
|
+
setPosition({ x: adjustedX, y: adjustedY });
|
|
202
|
+
}
|
|
203
|
+
}, [isCollapsed, bubbleSize, usePortal, align]);
|
|
204
|
+
/**
|
|
205
|
+
* Effect per resettare la posizione quando il parent o la finestra vengono ridimensionati
|
|
206
|
+
*/
|
|
207
|
+
useEffect(() => {
|
|
208
|
+
if (!buttonRef.current || !position)
|
|
164
209
|
return;
|
|
165
|
-
// Funzione per verificare e aggiustare la posizione dopo un resize
|
|
166
210
|
const handleResize = () => {
|
|
167
211
|
if (!buttonRef.current || !position)
|
|
168
212
|
return;
|
|
169
|
-
const parentRect = parent.getBoundingClientRect();
|
|
170
213
|
const rect = buttonRef.current.getBoundingClientRect();
|
|
171
|
-
// Spazio extra occupato dalla bubble quando non è collassato
|
|
172
214
|
const extraHeight = !isCollapsed ? bubbleSize.height : 0;
|
|
173
215
|
const extraWidth = !isCollapsed ? bubbleSize.width : 0;
|
|
174
|
-
// Calcola i nuovi limiti
|
|
175
216
|
let minX = 0;
|
|
176
|
-
let maxX
|
|
217
|
+
let maxX;
|
|
218
|
+
let maxY;
|
|
219
|
+
const bubbleBuffer = 5;
|
|
220
|
+
if (usePortal) {
|
|
221
|
+
maxX = window.innerWidth - rect.width;
|
|
222
|
+
maxY = window.innerHeight - rect.height + 20;
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
const parent = buttonRef.current.offsetParent;
|
|
226
|
+
if (!parent)
|
|
227
|
+
return;
|
|
228
|
+
const parentRect = parent.getBoundingClientRect();
|
|
229
|
+
maxX = parentRect.width - rect.width;
|
|
230
|
+
maxY = parentRect.height - rect.height + 20;
|
|
231
|
+
}
|
|
177
232
|
if (!isCollapsed) {
|
|
178
233
|
if (align === 'right') {
|
|
179
234
|
minX = Math.max(0, extraWidth - rect.width);
|
|
180
235
|
}
|
|
181
236
|
else {
|
|
182
|
-
maxX = Math.min(maxX,
|
|
237
|
+
maxX = Math.min(maxX, (usePortal ? window.innerWidth : buttonRef.current.offsetParent?.getBoundingClientRect().width || 0) - extraWidth);
|
|
183
238
|
}
|
|
184
239
|
}
|
|
185
|
-
const bubbleBuffer = 5; // Buffer per evitare che la bubble esca leggermente dai bordi
|
|
186
|
-
const maxY = parentRect.height - rect.height + 20; // +20px per permettere bottom: -20px
|
|
187
|
-
// Verifica se la posizione corrente è fuori dai limiti
|
|
188
240
|
const isOutOfBounds = position.x < minX ||
|
|
189
241
|
position.x > maxX ||
|
|
190
242
|
position.y < extraHeight + bubbleBuffer ||
|
|
191
243
|
position.y > maxY;
|
|
192
|
-
// Se è fuori dai limiti, resetta alla posizione default
|
|
193
244
|
if (isOutOfBounds) {
|
|
194
245
|
setPosition(null);
|
|
195
246
|
}
|
|
196
247
|
};
|
|
197
|
-
// Observer per
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
248
|
+
// Observer per il parent se non usa Portal
|
|
249
|
+
let resizeObserver;
|
|
250
|
+
if (!usePortal) {
|
|
251
|
+
const parent = buttonRef.current.offsetParent;
|
|
252
|
+
if (parent) {
|
|
253
|
+
resizeObserver = new ResizeObserver(handleResize);
|
|
254
|
+
resizeObserver.observe(parent);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
201
257
|
window.addEventListener('resize', handleResize);
|
|
202
258
|
return () => {
|
|
203
|
-
resizeObserver
|
|
259
|
+
resizeObserver?.disconnect();
|
|
204
260
|
window.removeEventListener('resize', handleResize);
|
|
205
261
|
};
|
|
206
|
-
}, [position, isCollapsed, bubbleSize, align]);
|
|
262
|
+
}, [position, isCollapsed, bubbleSize, align, usePortal]);
|
|
207
263
|
/**
|
|
208
264
|
* Effect per impostare automaticamente lo stato collassato su dispositivi mobile
|
|
209
265
|
* se non è stato specificato un valore iniziale.
|
|
@@ -218,7 +274,7 @@ const ToppyDraggableHelpCenter = ({ content, deviceType, align = 'right', onTopp
|
|
|
218
274
|
* Chiamato dal doppio click sul componente
|
|
219
275
|
*/
|
|
220
276
|
const toggleCollapse = (e) => {
|
|
221
|
-
e
|
|
277
|
+
e?.stopPropagation();
|
|
222
278
|
setIsCollapsed(!isCollapsed);
|
|
223
279
|
onToppyImageClick?.();
|
|
224
280
|
};
|
|
@@ -231,9 +287,6 @@ const ToppyDraggableHelpCenter = ({ content, deviceType, align = 'right', onTopp
|
|
|
231
287
|
if (!buttonRef.current)
|
|
232
288
|
return;
|
|
233
289
|
const rect = buttonRef.current.getBoundingClientRect();
|
|
234
|
-
const parentRect = buttonRef.current.offsetParent?.getBoundingClientRect();
|
|
235
|
-
if (!parentRect)
|
|
236
|
-
return;
|
|
237
290
|
// Calcola l'offset tra il punto di click e l'angolo superiore sinistro del componente
|
|
238
291
|
dragOffset.current = {
|
|
239
292
|
x: e.clientX - rect.left,
|
|
@@ -244,42 +297,56 @@ const ToppyDraggableHelpCenter = ({ content, deviceType, align = 'right', onTopp
|
|
|
244
297
|
};
|
|
245
298
|
/**
|
|
246
299
|
* Gestisce il movimento durante il trascinamento
|
|
247
|
-
* Calcola la nuova posizione rispettando i limiti del parent
|
|
300
|
+
* Calcola la nuova posizione rispettando i limiti del parent o viewport
|
|
248
301
|
* e tenendo conto delle dimensioni della speech bubble quando espansa
|
|
249
302
|
*/
|
|
250
303
|
const handleMouseMove = (e) => {
|
|
251
304
|
if (!isDragging || !buttonRef.current)
|
|
252
305
|
return;
|
|
253
|
-
const parentRect = buttonRef.current.offsetParent?.getBoundingClientRect();
|
|
254
|
-
if (!parentRect)
|
|
255
|
-
return;
|
|
256
306
|
const rect = buttonRef.current.getBoundingClientRect();
|
|
257
307
|
// Spazio extra occupato dalla bubble quando non è collassato
|
|
258
308
|
const extraHeight = !isCollapsed ? bubbleSize.height : 0;
|
|
259
309
|
const extraWidth = !isCollapsed ? bubbleSize.width : 0;
|
|
260
|
-
// Calcola i limiti orizzontali considerando la bubble
|
|
261
310
|
let minX = 0;
|
|
262
|
-
let maxX
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
311
|
+
let maxX;
|
|
312
|
+
let maxY;
|
|
313
|
+
let newX;
|
|
314
|
+
let newY;
|
|
315
|
+
const bubbleBuffer = 5;
|
|
316
|
+
if (usePortal) {
|
|
317
|
+
// Calcola i limiti usando il viewport
|
|
318
|
+
maxX = window.innerWidth - rect.width;
|
|
319
|
+
maxY = window.innerHeight - rect.height + 20;
|
|
320
|
+
if (!isCollapsed) {
|
|
321
|
+
if (align === 'right') {
|
|
322
|
+
minX = Math.max(0, extraWidth - rect.width);
|
|
323
|
+
}
|
|
324
|
+
else {
|
|
325
|
+
maxX = Math.min(maxX, window.innerWidth - extraWidth);
|
|
326
|
+
}
|
|
268
327
|
}
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
328
|
+
newX = Math.max(minX, Math.min(e.clientX - dragOffset.current.x, maxX));
|
|
329
|
+
newY = Math.max(extraHeight + bubbleBuffer, Math.min(e.clientY - dragOffset.current.y, maxY));
|
|
330
|
+
}
|
|
331
|
+
else {
|
|
332
|
+
// Calcola i limiti usando il parent
|
|
333
|
+
const parent = buttonRef.current.offsetParent;
|
|
334
|
+
if (!parent)
|
|
335
|
+
return;
|
|
336
|
+
const parentRect = parent.getBoundingClientRect();
|
|
337
|
+
maxX = parentRect.width - rect.width;
|
|
338
|
+
maxY = parentRect.height - rect.height + 20;
|
|
339
|
+
if (!isCollapsed) {
|
|
340
|
+
if (align === 'right') {
|
|
341
|
+
minX = Math.max(0, extraWidth - rect.width);
|
|
342
|
+
}
|
|
343
|
+
else {
|
|
344
|
+
maxX = Math.min(maxX, parentRect.width - extraWidth);
|
|
345
|
+
}
|
|
273
346
|
}
|
|
347
|
+
newX = Math.max(minX, Math.min(e.clientX - parentRect.left - dragOffset.current.x, maxX));
|
|
348
|
+
newY = Math.max(extraHeight + bubbleBuffer, Math.min(e.clientY - parentRect.top - dragOffset.current.y, maxY));
|
|
274
349
|
}
|
|
275
|
-
// Calcola la nuova posizione X rispettando i limiti
|
|
276
|
-
const newX = Math.max(minX, Math.min(e.clientX - parentRect.left - dragOffset.current.x, maxX));
|
|
277
|
-
// Calcola la nuova posizione Y rispettando i limiti
|
|
278
|
-
// Il limite superiore tiene conto dell'altezza della bubble + buffer di sicurezza
|
|
279
|
-
// Il limite inferiore include 20px extra per permettere bottom: -20px
|
|
280
|
-
const bubbleBuffer = 5; // Buffer per evitare che la bubble esca leggermente dai bordi
|
|
281
|
-
const maxY = parentRect.height - rect.height + 20;
|
|
282
|
-
const newY = Math.max(extraHeight + bubbleBuffer, Math.min(e.clientY - parentRect.top - dragOffset.current.y, maxY));
|
|
283
350
|
setPosition({ x: newX, y: newY });
|
|
284
351
|
};
|
|
285
352
|
/**
|
|
@@ -319,6 +386,8 @@ const ToppyDraggableHelpCenter = ({ content, deviceType, align = 'right', onTopp
|
|
|
319
386
|
}, [isDragging]);
|
|
320
387
|
// Renderizza l'overlay solo durante il drag
|
|
321
388
|
const renderDragOverlay = isDragging && _jsx(DragOverlay, {});
|
|
322
|
-
|
|
389
|
+
const toppyContent = (_jsxs(_Fragment, { children: [renderDragOverlay, _jsxs(ToppyButton, { ref: buttonRef, "$align": align, "$isDragging": isDragging, "$x": position?.x, "$y": position?.y, "$isVisible": isVisible, "$isCollapsed": isCollapsed, "$isMobile": isMobile, "$usePortal": usePortal, onMouseDown: !isMobile ? handleMouseDown : undefined, onContextMenu: (e) => e.preventDefault(), onDoubleClick: !isMobile ? toggleCollapse : undefined, children: [(content && !isCollapsed) && (_jsx(ToppySpeechBubble, { ref: bubbleRef, align: align, onClose: toggleCollapse, children: content })), _jsx("img", { src: Toppy, alt: "Toppy Help", draggable: false, onClick: isMobile ? toggleCollapse : undefined })] })] }));
|
|
390
|
+
// Renderizza nel document.body usando un Portal se usePortal è true, altrimenti renderizza normalmente
|
|
391
|
+
return usePortal ? ReactDOM.createPortal(toppyContent, document.body) : toppyContent;
|
|
323
392
|
};
|
|
324
393
|
export default ToppyDraggableHelpCenter;
|
|
@@ -3,6 +3,7 @@ interface ToppySpeechBubbleProps {
|
|
|
3
3
|
align?: 'left' | 'right';
|
|
4
4
|
children: React.ReactNode;
|
|
5
5
|
className?: string;
|
|
6
|
+
onClose?: () => void;
|
|
6
7
|
}
|
|
7
8
|
declare const ToppySpeechBubble: React.ForwardRefExoticComponent<ToppySpeechBubbleProps & React.RefAttributes<HTMLDivElement>>;
|
|
8
9
|
export default ToppySpeechBubble;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { forwardRef } from 'react';
|
|
3
3
|
import styled from 'styled-components';
|
|
4
|
+
import { SDKUI_Localizator } from '../../../helper';
|
|
4
5
|
// Styled component
|
|
5
6
|
const Bubble = styled.div `
|
|
6
7
|
position: absolute;
|
|
@@ -46,8 +47,60 @@ const Bubble = styled.div `
|
|
|
46
47
|
z-index: 2;
|
|
47
48
|
}
|
|
48
49
|
`;
|
|
50
|
+
const CloseButton = styled.button `
|
|
51
|
+
position: absolute;
|
|
52
|
+
top: -8px;
|
|
53
|
+
right: -8px;
|
|
54
|
+
width: 24px;
|
|
55
|
+
height: 24px;
|
|
56
|
+
border-radius: 50%;
|
|
57
|
+
border: 2px solid rgba(255, 255, 255, 0.9);
|
|
58
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
59
|
+
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4), 0 0 20px rgba(118, 75, 162, 0.2);
|
|
60
|
+
cursor: pointer;
|
|
61
|
+
display: flex;
|
|
62
|
+
align-items: center;
|
|
63
|
+
justify-content: center;
|
|
64
|
+
transition: all 0.3s cubic-bezier(0.68, -0.55, 0.265, 1.55);
|
|
65
|
+
z-index: 10001;
|
|
66
|
+
|
|
67
|
+
&::before,
|
|
68
|
+
&::after {
|
|
69
|
+
content: '';
|
|
70
|
+
position: absolute;
|
|
71
|
+
width: 11px;
|
|
72
|
+
height: 2px;
|
|
73
|
+
background: white;
|
|
74
|
+
border-radius: 2px;
|
|
75
|
+
transition: all 0.3s ease;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
&::before {
|
|
79
|
+
transform: rotate(45deg);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
&::after {
|
|
83
|
+
transform: rotate(-45deg);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
&:hover {
|
|
87
|
+
background: linear-gradient(135deg, #764ba2 0%, #f093fb 100%);
|
|
88
|
+
box-shadow: 0 6px 20px rgba(118, 75, 162, 0.6), 0 0 30px rgba(240, 147, 251, 0.4);
|
|
89
|
+
border-color: rgba(255, 255, 255, 1);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
&:active {
|
|
93
|
+
transform: rotate(90deg) scale(0.95);
|
|
94
|
+
box-shadow: 0 2px 8px rgba(102, 126, 234, 0.4);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
&:focus {
|
|
98
|
+
outline: none;
|
|
99
|
+
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4), 0 0 0 3px rgba(102, 126, 234, 0.2);
|
|
100
|
+
}
|
|
101
|
+
`;
|
|
49
102
|
// Componente con forwardRef
|
|
50
|
-
const ToppySpeechBubble = forwardRef(({ align = 'right', children, className }, ref) => {
|
|
51
|
-
return (
|
|
103
|
+
const ToppySpeechBubble = forwardRef(({ align = 'right', children, className, onClose }, ref) => {
|
|
104
|
+
return (_jsxs(Bubble, { ref: ref, "$align": align, className: className, children: [onClose && (_jsx(CloseButton, { onClick: onClose, "aria-label": SDKUI_Localizator.Close, title: SDKUI_Localizator.Close, type: "button" })), children] }));
|
|
52
105
|
});
|
|
53
106
|
export default ToppySpeechBubble;
|
|
@@ -1353,7 +1353,7 @@ const TMDcmtForm = ({ allTasks = [], getAllTasks, deleteTaskByIdsCallback, addTa
|
|
|
1353
1353
|
isEditable: true,
|
|
1354
1354
|
value: FormulaHelper.addFormulaTag(newFormula.expression)
|
|
1355
1355
|
}));
|
|
1356
|
-
} }), showApprovePopup && _jsx(WorkFlowApproveRejectPopUp, { deviceType: deviceType, onCompleted: handleWFOperationCompleted, TID: approvalVID, DID: DID, isReject: 0, onClose: () => setShowApprovePopup(false) }), showRejectPopup && _jsx(WorkFlowApproveRejectPopUp, { deviceType: deviceType, onCompleted: handleWFOperationCompleted, TID: approvalVID, DID: DID, isReject: 1, onClose: () => setShowRejectPopup(false) }), showReAssignPopup && _jsx(WorkFlowReAssignPopUp, { deviceType: deviceType, onCompleted: handleWFOperationCompleted, TID: approvalVID, DID: DID, onClose: () => setShowReAssignPopup(false) }), showMoreInfoPopup && _jsx(WorkFlowMoreInfoPopUp, { deviceType: deviceType, onCompleted: handleWFOperationCompleted, TID: approvalVID, DID: DID, onClose: () => setShowMoreInfoPopup(false) }), (isModal && onClose) && _jsx("div", { id: "TMDcmtFormShowConfirmForClose-" + id })] }),
|
|
1356
|
+
} }), showApprovePopup && _jsx(WorkFlowApproveRejectPopUp, { deviceType: deviceType, onCompleted: handleWFOperationCompleted, TID: approvalVID, DID: DID, isReject: 0, onClose: () => setShowApprovePopup(false) }), showRejectPopup && _jsx(WorkFlowApproveRejectPopUp, { deviceType: deviceType, onCompleted: handleWFOperationCompleted, TID: approvalVID, DID: DID, isReject: 1, onClose: () => setShowRejectPopup(false) }), showReAssignPopup && _jsx(WorkFlowReAssignPopUp, { deviceType: deviceType, onCompleted: handleWFOperationCompleted, TID: approvalVID, DID: DID, onClose: () => setShowReAssignPopup(false) }), showMoreInfoPopup && _jsx(WorkFlowMoreInfoPopUp, { deviceType: deviceType, onCompleted: handleWFOperationCompleted, TID: approvalVID, DID: DID, onClose: () => setShowMoreInfoPopup(false) }), (isModal && onClose) && _jsx("div", { id: "TMDcmtFormShowConfirmForClose-" + id })] }), _jsx(ToppyDraggableHelpCenter, { initialIsCollapsed: false, deviceType: deviceType, isVisible: showToppyForApprove || showToppyForCompleteMoreInfo || showToppyForReferences, content: _jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: '10px' }, children: [showToppyForApprove && (workItems.length === 1 ?
|
|
1357
1357
|
_jsx(WorkFlowOperationButtons, { deviceType: deviceType, onApprove: () => setShowApprovePopup(true), onSignApprove: handleSignApprove, onReject: () => setShowRejectPopup(true), onReAssign: () => setShowReAssignPopup(true), onMoreInfo: () => setShowMoreInfoPopup(true), dtd: fromDTD })
|
|
1358
1358
|
:
|
|
1359
1359
|
_jsxs("div", { style: { padding: 10, color: 'white', maxWidth: '180px', borderRadius: 10, background: '#1B1464 0% 0% no-repeat padding-box', border: '1px solid #FFFFFF' }, children: [`Devi approvare ${workItems.length} workitem(s) per questo documento.`, `Vai alla sezione di approvazione.`] })), showToppyForCompleteMoreInfo && (_jsxs(_Fragment, { children: [_jsx("div", { style: { padding: 10, color: 'white', maxWidth: '180px', borderRadius: 10, background: '#1B1464 0% 0% no-repeat padding-box', border: '1px solid #FFFFFF' }, children: `${SDKUI_Localizator.MoreInfoCompleteRequestSentBy} ${taskMoreInfo?.fromName}!` }), _jsx(TMButton, { caption: SDKUI_Localizator.CommentAndComplete, color: 'success', showTooltip: false, onClick: () => {
|
|
@@ -1368,7 +1368,7 @@ const TMDcmtForm = ({ allTasks = [], getAllTasks, deleteTaskByIdsCallback, addTa
|
|
|
1368
1368
|
backgroundColor: 'rgba(255,255,255,0.2)',
|
|
1369
1369
|
margin: '6px 0'
|
|
1370
1370
|
} })), _jsxs(StyledReferenceButton, { onClick: () => handleNavigateToReference(ref), children: [_jsx("span", { children: label }), _jsx("span", { children: `"${ref.objName}"` })] }, `ref-${index}-${ref.objID}`)] }, `ref-frag-${index}-${ref.objID}`));
|
|
1371
|
-
})] }) })
|
|
1371
|
+
})] }) })] }), (showCommentForm && TID && DID) &&
|
|
1372
1372
|
_jsx(TMBlogCommentForm, { context: { engine: 'SearchEngine', object: { tid: TID, did: DID } }, onClose: () => setShowCommentForm(false), refreshCallback: handleCompleteMoreInfo, participants: [], showAttachmentsSection: false, allArchivedDocumentsFileItems: [] }), isOpenDetails &&
|
|
1373
1373
|
_jsx(StyledModalContainer, { children: _jsx(TMMasterDetailDcmts, { deviceType: deviceType, isForMaster: false, inputDcmts: getSelectionDcmtInfo(), allowNavigation: allowNavigation, canNext: canNext, canPrev: canPrev, onNext: onNext, onPrev: onPrev, onBack: () => setIsOpenDetails(false), allTasks: allTasks, getAllTasks: getAllTasks, deleteTaskByIdsCallback: deleteTaskByIdsCallback, addTaskCallback: addTaskCallback, editTaskCallback: editTaskCallback, handleNavigateToWGs: handleNavigateToWGs, handleNavigateToDossiers: handleNavigateToDossiers }) }), isOpenMaster &&
|
|
1374
1374
|
_jsxs(StyledModalContainer, { children: [_jsx(TMMasterDetailDcmts, { deviceType: deviceType, inputDcmts: getSelectionDcmtInfo(), isForMaster: true, allowNavigation: allowNavigation, canNext: canNext, canPrev: canPrev, onNext: onNext, onPrev: onPrev, onBack: () => setIsOpenMaster(false), appendMasterDcmts: handleAddItem, allTasks: allTasks, getAllTasks: getAllTasks, deleteTaskByIdsCallback: deleteTaskByIdsCallback, addTaskCallback: addTaskCallback, editTaskCallback: editTaskCallback, handleNavigateToWGs: handleNavigateToWGs, handleNavigateToDossiers: handleNavigateToDossiers }), secondaryMasterDcmts.length > 0 && secondaryMasterDcmts.map((dcmt, index) => {
|
|
@@ -3,7 +3,6 @@ import { useCallback, useEffect, useRef, useState } from 'react';
|
|
|
3
3
|
import { PlatformObjectValidator, WhereItem, SDK_Localizator, OrderByItem, SelectItem, SelectItemVisibilities, SDK_Globals, SavedQueryCacheService, SearchEngine, QueryOperators } from '@topconsultnpm/sdk-ts';
|
|
4
4
|
import styled from 'styled-components';
|
|
5
5
|
import TMSearchQueryEditor from './TMSearchQueryEditor';
|
|
6
|
-
import Toppy from '../../../assets/Toppy-generico.png';
|
|
7
6
|
import { getDcmtTypesByQdAsync, SDKUI_Localizator, getQD, IconMenuVertical, IconAddCircleOutline, IconEdit, IconEasy, IconAdvanced, deepCompare, IconSearch, IconClear, getDefaultOperator, prepareQdForSearchAsync, IsParametricQuery, SDKUI_Globals, IconArrowRight, IconMenuCAArchive, getListMaxItems } from '../../../helper';
|
|
8
7
|
import { useQueryParametersDialog } from '../../../hooks/useQueryParametersDialog';
|
|
9
8
|
import { FormModes } from '../../../ts';
|
|
@@ -13,7 +12,6 @@ import ShowAlert from '../../base/TMAlert';
|
|
|
13
12
|
import TMButton from '../../base/TMButton';
|
|
14
13
|
import { useDeviceType, DeviceType } from '../../base/TMDeviceProvider';
|
|
15
14
|
import TMDropDownMenu from '../../base/TMDropDownMenu';
|
|
16
|
-
import TMLayoutContainer from '../../base/TMLayout';
|
|
17
15
|
import { TMExceptionBoxManager } from '../../base/TMPopUp';
|
|
18
16
|
import TMSpinner from '../../base/TMSpinner';
|
|
19
17
|
import TMPanel from '../../base/TMPanel';
|
|
@@ -22,6 +20,7 @@ import { TMMetadataChooserForm } from '../../choosers/TMMetadataChooser';
|
|
|
22
20
|
import TMQueryEditor from '../../query/TMQueryEditor';
|
|
23
21
|
import TMSavedQueryForm from './TMSavedQueryForm';
|
|
24
22
|
import { AdvancedMenuButtons } from '../../editors/TMMetadataValues';
|
|
23
|
+
import TMToppyMessage from '../../../helper/TMToppyMessage';
|
|
25
24
|
const TMSearchQueryPanel = ({ fromDTD, showBackToResultButton, isExpertMode = SDKUI_Globals.userSettings.advancedSettings.expertMode === 1, SQD, inputMids, onSearchCompleted, onSqdSaved, onBack, onClosePanel, allowMaximize = true, onMaximizePanel, onBackToResult, passToArchiveCallback }) => {
|
|
26
25
|
const [confirmQueryParams, ConfirmQueryParamsDialog] = useQueryParametersDialog();
|
|
27
26
|
const [qd, setQd] = useState();
|
|
@@ -329,7 +328,7 @@ const TMSearchQueryPanel = ({ fromDTD, showBackToResultButton, isExpertMode = SD
|
|
|
329
328
|
_jsx(TMMetadataChooserForm, { allowMultipleSelection: true, height: '500px', width: '600px', allowSysMetadata: true, qd: qd, selectedIDs: qd?.select?.map((item) => ({ tid: item.tid, mid: item.mid })), onClose: handleCloseOutputConfig, onChoose: handleChooseOutput }), showOrderByConfig &&
|
|
330
329
|
_jsx(TMMetadataChooserForm, { allowMultipleSelection: true, height: '500px', width: '600px', allowSysMetadata: true, qd: qd, selectedIDs: qd?.orderBy?.map((item) => ({ tid: item.tid, mid: item.mid })), onClose: handleCloseOrderByConfig, onChoose: handleChooseOrderBy })] })
|
|
331
330
|
:
|
|
332
|
-
|
|
331
|
+
_jsx(TMToppyMessage, { message: SDKUI_Localizator.DcmtTypeSelectOrQuickSearch, titleTooltip: SDKUI_Localizator.DcmtTypeSelectOrQuickSearch }), showSqdForm &&
|
|
333
332
|
_jsx(StyledModalContainer, { style: { backgroundColor: `${TMColors.backgroundColorHeader}12` }, children: _jsx(TMSavedQueryForm, { height: '50%', width: '50%', id: formModeSqdForm === FormModes.Create ? -1 : SQD?.id, title: 'Ricerca rapida', formMode: formModeSqdForm, showBackButton: true, qd: qd, isAdvancedSearch: showAdvancedSearch, isModal: false, onClose: () => { setShowSqdForm(false); }, onSaved: onSqdSaved }) })] }), showDistinctValuesPanel &&
|
|
334
333
|
_jsx(TMDistinctValues, { isModal: true, tid: focusedTidMid?.tid, mid: focusedTidMid?.mid, separator: ',', onClosePanelCallback: () => setShowDistinctValuesPanel(false), onSelectionChanged: (e) => {
|
|
335
334
|
if (!e)
|
|
@@ -584,18 +584,17 @@ const TMSearchResult = ({ allTasks = [], getAllTasks, deleteTaskByIdsCallback, a
|
|
|
584
584
|
setIsOpenBatchUpdate(false);
|
|
585
585
|
setIsModifiedBatchUpdate(false);
|
|
586
586
|
await refreshSelectionDataRowsAsync();
|
|
587
|
-
}, onStatusChanged: (isModified) => { setIsModifiedBatchUpdate(isModified); } }), (showToppyForApprove && !showApprovePopup && !showRejectPopup && !showReAssignPopup && !showMoreInfoPopup && !openS4TViewer && !showTodoDcmtForm)
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
}, approveDisable: selectedDocs.length === 0, signApproveDisable: disableSignApproveDisable, rejectDisable: selectedDocs.length === 0, reassignDisable: selectedDocs.length === 0, infoDisable: selectedDocs.length !== 1, dtd: fromDTD }) }) })] }), _jsx(ConfirmFormatDialog, {}), _jsx(ConfirmAttachmentsDialog, {}), customButton && _jsx(TMCustomButton, { button: customButton, formData: currentMetadataValues, selectedItems: selectedItems, onClose: () => setCustomButton(undefined) }), showRelatedDcmtsChooser &&
|
|
587
|
+
}, onStatusChanged: (isModified) => { setIsModifiedBatchUpdate(isModified); } }), _jsx(ToppyDraggableHelpCenter, { initialIsCollapsed: false, deviceType: deviceType, usePortal: true, isVisible: showToppyForApprove && !showApprovePopup && !showRejectPopup && !showReAssignPopup && !showMoreInfoPopup && !openS4TViewer && !showTodoDcmtForm, content: _jsx("div", { style: { display: 'flex', flexDirection: 'column', gap: '10px' }, children: _jsx(WorkFlowOperationButtons, { deviceType: deviceType, onApprove: () => {
|
|
588
|
+
setShowApprovePopup(true);
|
|
589
|
+
}, onSignApprove: () => {
|
|
590
|
+
handleSignApprove();
|
|
591
|
+
}, onReject: () => {
|
|
592
|
+
setShowRejectPopup(true);
|
|
593
|
+
}, onReAssign: () => {
|
|
594
|
+
setShowReAssignPopup(true);
|
|
595
|
+
}, onMoreInfo: () => {
|
|
596
|
+
setShowMoreInfoPopup(true);
|
|
597
|
+
}, approveDisable: selectedDocs.length === 0, signApproveDisable: disableSignApproveDisable, rejectDisable: selectedDocs.length === 0, reassignDisable: selectedDocs.length === 0, infoDisable: selectedDocs.length !== 1, dtd: fromDTD }) }) })] }), _jsx(ConfirmFormatDialog, {}), _jsx(ConfirmAttachmentsDialog, {}), customButton && _jsx(TMCustomButton, { button: customButton, formData: currentMetadataValues, selectedItems: selectedItems, onClose: () => setCustomButton(undefined) }), showRelatedDcmtsChooser &&
|
|
599
598
|
_jsx(TMChooserForm, { dataSource: relatedDcmtsChooserDataSource, onChoose: async (selectedRelation) => {
|
|
600
599
|
try {
|
|
601
600
|
setShowRelatedDcmtsChooser(false);
|
|
@@ -59,7 +59,6 @@ export { default as TMBlogAttachments } from './grids/TMBlogAttachments';
|
|
|
59
59
|
export { default as TMBlogCommentForm } from './features/blog/TMBlogCommentForm';
|
|
60
60
|
export * from './query/TMQueryEditor';
|
|
61
61
|
export * from './query/TMQuerySummary';
|
|
62
|
-
export { default as ToppyHelpCenter } from './features/assistant/ToppyHelpCenter';
|
|
63
62
|
export { default as ToppyDraggableHelpCenter } from './features/assistant/ToppyDraggableHelpCenter';
|
|
64
63
|
export * from './features/documents/TMDcmtForm';
|
|
65
64
|
export * from './features/documents/TMDcmtIcon';
|
package/lib/components/index.js
CHANGED
|
@@ -66,7 +66,6 @@ export { default as TMBlogCommentForm } from './features/blog/TMBlogCommentForm'
|
|
|
66
66
|
export * from './query/TMQueryEditor';
|
|
67
67
|
export * from './query/TMQuerySummary';
|
|
68
68
|
//assistant
|
|
69
|
-
export { default as ToppyHelpCenter } from './features/assistant/ToppyHelpCenter';
|
|
70
69
|
export { default as ToppyDraggableHelpCenter } from './features/assistant/ToppyDraggableHelpCenter';
|
|
71
70
|
//documents
|
|
72
71
|
export * from './features/documents/TMDcmtForm';
|
|
@@ -34,9 +34,10 @@ const StyledToppyImage = styled.img `
|
|
|
34
34
|
max-width: 120px;
|
|
35
35
|
height: auto;
|
|
36
36
|
display: block;
|
|
37
|
+
user-select: none;
|
|
37
38
|
`;
|
|
38
39
|
const TMToppyMessage = (props) => {
|
|
39
40
|
const { message, titleTooltip } = props;
|
|
40
|
-
return (_jsxs(TMLayoutContainer, { gap: 30, alignItems: "center", justifyContent: "center", onContextMenu: (e) => e.preventDefault(), children: [_jsx(StyledToppyTextContainer, { children: _jsx(StyledToppyText, { title: titleTooltip || undefined, children: message }) }), _jsx(StyledToppyImage, { src: Toppy, alt: "Toppy" })] }));
|
|
41
|
+
return (_jsxs(TMLayoutContainer, { gap: 30, alignItems: "center", justifyContent: "center", onContextMenu: (e) => e.preventDefault(), children: [_jsx(StyledToppyTextContainer, { children: _jsx(StyledToppyText, { title: titleTooltip || undefined, children: message }) }), _jsx(StyledToppyImage, { src: Toppy, alt: "Toppy", draggable: false, onMouseDown: (e) => e.stopPropagation() })] }));
|
|
41
42
|
};
|
|
42
43
|
export default TMToppyMessage;
|
package/package.json
CHANGED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { DeviceType } from '../../base/TMDeviceProvider';
|
|
3
|
-
interface ToppyHelpCenterProps {
|
|
4
|
-
content?: React.ReactNode;
|
|
5
|
-
deviceType?: DeviceType;
|
|
6
|
-
usePortal?: boolean;
|
|
7
|
-
align?: 'right' | 'left';
|
|
8
|
-
initialIsCollapsed?: boolean;
|
|
9
|
-
onToppyImageClick?: () => void;
|
|
10
|
-
}
|
|
11
|
-
declare const ToppyHelpCenter: React.FC<ToppyHelpCenterProps>;
|
|
12
|
-
export default ToppyHelpCenter;
|
|
@@ -1,173 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useEffect, useState } from 'react';
|
|
3
|
-
import ReactDOM from 'react-dom';
|
|
4
|
-
import styled, { keyframes, css } from 'styled-components';
|
|
5
|
-
import Toppy from '../../../assets/Toppy-generico.png';
|
|
6
|
-
import { DeviceType } from '../../base/TMDeviceProvider';
|
|
7
|
-
const toppyEntrance = keyframes `
|
|
8
|
-
0% { right: -200px; opacity: 0; }
|
|
9
|
-
60% { opacity: 1; }
|
|
10
|
-
100% { right: 10px; opacity: 1; }
|
|
11
|
-
`;
|
|
12
|
-
const pulseAnimation = keyframes `
|
|
13
|
-
0% { transform: scale(1); }
|
|
14
|
-
50% { transform: scale(1.05); }
|
|
15
|
-
100% { transform: scale(1); }
|
|
16
|
-
`;
|
|
17
|
-
const bounceAnimation = keyframes `
|
|
18
|
-
0%, 20%, 50%, 80%, 100% {
|
|
19
|
-
transform: translateY(0);
|
|
20
|
-
}
|
|
21
|
-
40% {
|
|
22
|
-
transform: translateY(-10px);
|
|
23
|
-
}
|
|
24
|
-
60% {
|
|
25
|
-
transform: translateY(-5px);
|
|
26
|
-
}
|
|
27
|
-
`;
|
|
28
|
-
const wiggle = keyframes `
|
|
29
|
-
0% { transform: rotate(-5deg) scale(1.1); }
|
|
30
|
-
10% { transform: rotate(5deg) scale(0.95); }
|
|
31
|
-
20% { transform: rotate(-5deg) scale(1.1); }
|
|
32
|
-
30% { transform: rotate(5deg) scale(1.05); }
|
|
33
|
-
40% { transform: rotate(-5deg) scale(1); }
|
|
34
|
-
50% { transform: rotate(5deg) scale(0.96); }
|
|
35
|
-
60% { transform: rotate(-5deg) scale(1.15); }
|
|
36
|
-
70% { transform: rotate(5deg) scale(0.99); }
|
|
37
|
-
80% { transform: rotate(-5deg) scale(1.05); }
|
|
38
|
-
90% { transform: rotate(5deg) scale(1); }
|
|
39
|
-
100% { transform: rotate(-5deg) scale(1.08); }
|
|
40
|
-
`;
|
|
41
|
-
const ToppyContainer = styled.div `
|
|
42
|
-
position: ${({ $fixed }) => ($fixed ? 'fixed' : 'absolute')};
|
|
43
|
-
bottom: ${({ $isCollapsed, $isMobile }) => $isMobile ? '5px' : $isCollapsed ? '5px' : '-20px'};
|
|
44
|
-
${({ $align, $isCollapsed }) => $align === 'left'
|
|
45
|
-
? `left: ${$isCollapsed ? '5px' : '10px'}; right: auto;`
|
|
46
|
-
: `right: ${$isCollapsed ? '5px' : '10px'}; left: auto;`}
|
|
47
|
-
display: flex;
|
|
48
|
-
flex-direction: column-reverse; /* Il contenuto è sopra l'immagine */
|
|
49
|
-
align-items: ${({ $align }) => $align === 'left' ? 'flex-start' : 'flex-end'};
|
|
50
|
-
animation: ${({ $fixed }) => $fixed && css `${toppyEntrance} 0.5s cubic-bezier(0.23, 1, 0.32, 1)`};
|
|
51
|
-
z-index: ${({ $fixed }) => ($fixed ? 2147483647 : 10)};
|
|
52
|
-
`;
|
|
53
|
-
const ToppyImage = styled.img `
|
|
54
|
-
width: ${({ $isMobile, $isCollapsed }) => $isCollapsed ? '60px' : $isMobile ? '90px' : '120px'};
|
|
55
|
-
height: ${({ $isMobile, $isCollapsed }) => $isCollapsed ? '70px' : $isMobile ? '105px' : '140px'};
|
|
56
|
-
cursor: pointer;
|
|
57
|
-
object-fit: cover;
|
|
58
|
-
object-position: top center;
|
|
59
|
-
clip-path: inset(0 0 22% 0 round 10px);
|
|
60
|
-
transform: ${({ $isCollapsed, $align }) => $isCollapsed
|
|
61
|
-
? 'none'
|
|
62
|
-
: $align === 'left'
|
|
63
|
-
? 'rotate(20deg)'
|
|
64
|
-
: 'rotate(-20deg)'};
|
|
65
|
-
|
|
66
|
-
${({ $isCollapsed }) => $isCollapsed &&
|
|
67
|
-
css `
|
|
68
|
-
/* animation: ${bounceAnimation} 2s infinite; */
|
|
69
|
-
/* animation: ${pulseAnimation} 2s infinite; */
|
|
70
|
-
animation: ${wiggle} 4s infinite;
|
|
71
|
-
`}
|
|
72
|
-
`;
|
|
73
|
-
const ToppyContent = styled.div `
|
|
74
|
-
margin-bottom: ${({ $isMobile, $align }) => $align === 'left'
|
|
75
|
-
? '30px' // Spazio tra ToppyContent e ToppyImage
|
|
76
|
-
: $isMobile
|
|
77
|
-
? '30px'
|
|
78
|
-
: '20px'};
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
display: ${props => (props.$isCollapsed ? 'none' : 'block')};
|
|
82
|
-
width: max-content;
|
|
83
|
-
max-width: 250px;
|
|
84
|
-
background: linear-gradient(
|
|
85
|
-
180deg,
|
|
86
|
-
rgba(0, 113, 188, 0.45) 0%,
|
|
87
|
-
rgba(27, 20, 100, 0.65) 100%
|
|
88
|
-
);
|
|
89
|
-
color: white;
|
|
90
|
-
padding: 10px;
|
|
91
|
-
border-radius: 10px;
|
|
92
|
-
border: 1px solid #FFFFFF;
|
|
93
|
-
opacity: ${props => (props.$isCollapsed ? 0 : 1)};
|
|
94
|
-
transform: ${props => (props.$isCollapsed ? 'translateY(20px)' : 'translateY(0)')};
|
|
95
|
-
transition: opacity 0.3s ease, transform 0.3s ease;
|
|
96
|
-
pointer-events: ${props => (props.$isCollapsed ? 'none' : 'auto')};
|
|
97
|
-
position: relative;
|
|
98
|
-
|
|
99
|
-
${({ $align, $isMobile }) => $align === 'left' &&
|
|
100
|
-
css `
|
|
101
|
-
position: absolute;
|
|
102
|
-
left: 50px;
|
|
103
|
-
right: auto;
|
|
104
|
-
bottom: 100%;
|
|
105
|
-
transform: translateY(-${$isMobile ? '10px' : '20px'});
|
|
106
|
-
`}
|
|
107
|
-
|
|
108
|
-
&::after {
|
|
109
|
-
transform: ${({ $align }) => $align === 'left' ? 'skewX(15deg)' : 'skewX(-15deg)'};
|
|
110
|
-
content: "";
|
|
111
|
-
position: absolute;
|
|
112
|
-
top: 100%;
|
|
113
|
-
|
|
114
|
-
${({ $align }) => $align === 'left' ? 'left: 20px; right: auto;' : 'right: 15px; left: auto;'}
|
|
115
|
-
border-width: 32px 32px 0 0;
|
|
116
|
-
border-style: solid;
|
|
117
|
-
border-color: #FFFFFF transparent;
|
|
118
|
-
display: block;
|
|
119
|
-
width: 0;
|
|
120
|
-
height: 0;
|
|
121
|
-
z-index: 1;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
&::before {
|
|
125
|
-
transform: ${({ $align }) => $align === 'left' ? 'skewX(15deg)' : 'skewX(-15deg)'};
|
|
126
|
-
content: "";
|
|
127
|
-
position: absolute;
|
|
128
|
-
top: 100%;
|
|
129
|
-
|
|
130
|
-
${({ $align }) => $align === 'left' ? 'left: 20px; right: auto;' : 'right: 15px; left: auto;'}
|
|
131
|
-
border-width: 32px 32px 0 0;
|
|
132
|
-
border-style: solid;
|
|
133
|
-
border-color: rgba(27, 20, 100, 0.65) transparent;
|
|
134
|
-
display: block;
|
|
135
|
-
width: 0;
|
|
136
|
-
height: 0;
|
|
137
|
-
z-index: 2;
|
|
138
|
-
}
|
|
139
|
-
`;
|
|
140
|
-
const ToppyHelpCenter = ({ content, deviceType, usePortal = true, align = 'right', initialIsCollapsed, onToppyImageClick }) => {
|
|
141
|
-
const [isCollapsed, setIsCollapsed] = useState(initialIsCollapsed ?? false);
|
|
142
|
-
const [portalContainer, setPortalContainer] = useState(null);
|
|
143
|
-
useEffect(() => {
|
|
144
|
-
if (initialIsCollapsed === undefined && deviceType === DeviceType.MOBILE) {
|
|
145
|
-
setIsCollapsed(true);
|
|
146
|
-
}
|
|
147
|
-
}, [deviceType, initialIsCollapsed]);
|
|
148
|
-
useEffect(() => {
|
|
149
|
-
if (!usePortal)
|
|
150
|
-
return;
|
|
151
|
-
const portalRoot = document.createElement('div');
|
|
152
|
-
portalRoot.setAttribute('id', 'toppy-portal-root');
|
|
153
|
-
document.body.appendChild(portalRoot);
|
|
154
|
-
setPortalContainer(portalRoot);
|
|
155
|
-
return () => {
|
|
156
|
-
document.body.removeChild(portalRoot);
|
|
157
|
-
};
|
|
158
|
-
}, [usePortal]);
|
|
159
|
-
const toggleCollapse = (e) => {
|
|
160
|
-
e.stopPropagation();
|
|
161
|
-
setIsCollapsed(!isCollapsed);
|
|
162
|
-
onToppyImageClick?.();
|
|
163
|
-
};
|
|
164
|
-
const isMobile = deviceType === DeviceType.MOBILE;
|
|
165
|
-
const toppyComponent = (_jsxs(ToppyContainer, { "$isMobile": isMobile, "$isCollapsed": isCollapsed, "$fixed": usePortal, "$align": align, onContextMenu: (e) => e.preventDefault(), children: [_jsx(ToppyImage, { "$isMobile": isMobile, "$isCollapsed": isCollapsed, "$align": align, onClick: toggleCollapse, src: Toppy, alt: "Toppy" }), _jsx(ToppyContent, { "$isCollapsed": isCollapsed, "$isMobile": isMobile, "$align": align, children: content })] }));
|
|
166
|
-
if (usePortal) {
|
|
167
|
-
if (!portalContainer)
|
|
168
|
-
return null;
|
|
169
|
-
return ReactDOM.createPortal(toppyComponent, portalContainer);
|
|
170
|
-
}
|
|
171
|
-
return toppyComponent;
|
|
172
|
-
};
|
|
173
|
-
export default ToppyHelpCenter;
|