@topconsultnpm/sdkui-react 6.20.0-dev1.21 → 6.20.0-dev1.23
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/TMCustomButton.js +41 -4
- package/lib/components/features/documents/TMDcmtPreview.js +4 -100
- package/lib/components/features/search/TMSearchResult.js +1 -1
- package/lib/helper/TMPdfViewer.d.ts +8 -0
- package/lib/helper/TMPdfViewer.js +170 -0
- package/lib/helper/index.d.ts +1 -0
- package/lib/helper/index.js +1 -0
- package/package.json +1 -1
- package/lib/components/NewComponents/Notification/Notification.d.ts +0 -4
- package/lib/components/NewComponents/Notification/Notification.js +0 -60
- package/lib/components/NewComponents/Notification/NotificationContainer.d.ts +0 -8
- package/lib/components/NewComponents/Notification/NotificationContainer.js +0 -33
- package/lib/components/NewComponents/Notification/index.d.ts +0 -2
- package/lib/components/NewComponents/Notification/index.js +0 -2
- package/lib/components/NewComponents/Notification/styles.d.ts +0 -21
- package/lib/components/NewComponents/Notification/styles.js +0 -180
- package/lib/components/NewComponents/Notification/types.d.ts +0 -18
- package/lib/components/NewComponents/Notification/types.js +0 -1
|
@@ -4,18 +4,49 @@ import TMModal from './TMModal';
|
|
|
4
4
|
import styled from 'styled-components';
|
|
5
5
|
import { SDKUI_Localizator, TMLayoutWaitingContainer } from '../..';
|
|
6
6
|
import { getButtonAttributes, getSelectedItem } from '../../helper/dcmtsHelper';
|
|
7
|
+
import { DeviceType, useDeviceType } from './TMDeviceProvider';
|
|
7
8
|
const IframeContainer = styled.div `
|
|
8
9
|
display: flex;
|
|
9
10
|
height: 100%;
|
|
10
11
|
flex-direction: column;
|
|
12
|
+
position: relative;
|
|
11
13
|
`;
|
|
12
14
|
const StyledIframe = styled.iframe `
|
|
13
15
|
border: none;
|
|
14
16
|
flex: 1;
|
|
15
17
|
`;
|
|
18
|
+
const LoadingOverlay = styled.div `
|
|
19
|
+
position: absolute;
|
|
20
|
+
top: 0;
|
|
21
|
+
left: 0;
|
|
22
|
+
right: 0;
|
|
23
|
+
bottom: 0;
|
|
24
|
+
background-color: rgba(128, 128, 128, 0.3);
|
|
25
|
+
display: flex;
|
|
26
|
+
align-items: center;
|
|
27
|
+
justify-content: center;
|
|
28
|
+
z-index: 1000;
|
|
29
|
+
|
|
30
|
+
&::after {
|
|
31
|
+
content: '';
|
|
32
|
+
width: 40px;
|
|
33
|
+
height: 40px;
|
|
34
|
+
border: 4px solid rgba(255, 255, 255, 0.3);
|
|
35
|
+
border-top-color: #fff;
|
|
36
|
+
border-radius: 50%;
|
|
37
|
+
animation: spin 0.8s linear infinite;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
@keyframes spin {
|
|
41
|
+
to {
|
|
42
|
+
transform: rotate(360deg);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
`;
|
|
16
46
|
const TMCustomButton = (props) => {
|
|
17
47
|
const { button, isModal = true, formData, selectedItems, onClose } = props;
|
|
18
48
|
const { appName: scriptUrl, arguments: args } = button;
|
|
49
|
+
const Device = useDeviceType();
|
|
19
50
|
const iframeRef = useRef(null);
|
|
20
51
|
const attributes = useMemo(() => getButtonAttributes(args, formData, selectedItems), [args, formData, selectedItems]);
|
|
21
52
|
const RunOnce = button.mode === "RunOnce";
|
|
@@ -28,6 +59,13 @@ const TMCustomButton = (props) => {
|
|
|
28
59
|
const [waitPanelValue, setWaitPanelValue] = useState(0);
|
|
29
60
|
const [waitPanelMaxValue, setWaitPanelMaxValue] = useState(selectedItemsCount);
|
|
30
61
|
const [abortController, setAbortController] = useState(undefined);
|
|
62
|
+
// Aggiungi timestamp all'URL per evitare cache
|
|
63
|
+
const iframeUrl = useMemo(() => {
|
|
64
|
+
if (!scriptUrl)
|
|
65
|
+
return '';
|
|
66
|
+
const separator = scriptUrl.includes('?') ? '&' : '?';
|
|
67
|
+
return `${scriptUrl}${separator}t=${Date.now()}`;
|
|
68
|
+
}, [scriptUrl]);
|
|
31
69
|
const targetOrigin = useMemo(() => {
|
|
32
70
|
if (!scriptUrl)
|
|
33
71
|
return '*';
|
|
@@ -40,6 +78,7 @@ const TMCustomButton = (props) => {
|
|
|
40
78
|
}
|
|
41
79
|
}, [scriptUrl]);
|
|
42
80
|
const handleLoad = () => setLoading(false);
|
|
81
|
+
const isMobile = Device === DeviceType.MOBILE;
|
|
43
82
|
const handleError = () => {
|
|
44
83
|
setLoading(false);
|
|
45
84
|
setError(true);
|
|
@@ -81,7 +120,6 @@ const TMCustomButton = (props) => {
|
|
|
81
120
|
useEffect(() => {
|
|
82
121
|
if (loading || error)
|
|
83
122
|
return;
|
|
84
|
-
//if(error) clearTimeout(timeoutIframe);
|
|
85
123
|
if (!RunOnce && selectedItemsCount > 0) {
|
|
86
124
|
// esegui per ogni item selezionato
|
|
87
125
|
const controller = new AbortController();
|
|
@@ -102,7 +140,6 @@ const TMCustomButton = (props) => {
|
|
|
102
140
|
onClose?.();
|
|
103
141
|
}, 2000);
|
|
104
142
|
}
|
|
105
|
-
//clearTimeout(timeoutIframe);
|
|
106
143
|
}
|
|
107
144
|
}, [loading, error, RunOnce]);
|
|
108
145
|
useEffect(() => {
|
|
@@ -111,7 +148,7 @@ const TMCustomButton = (props) => {
|
|
|
111
148
|
onClose?.();
|
|
112
149
|
}
|
|
113
150
|
}, []);
|
|
114
|
-
const iframeContent = (_jsxs(IframeContainer, { style: !RunOnce ? { visibility: 'hidden' } : {}, children: [error && _jsx("div", { children: "Si \u00E8 verificato un errore nel caricamento del contenuto." }), !error && _jsx(StyledIframe, { ref: iframeRef, loading: 'lazy', onLoad: handleLoad, onError: handleError, src:
|
|
115
|
-
return isModal && RunOnce ? (_jsx(TMModal, { title: button.title, width: '60%', height: '70%', resizable: true, expandable: true, onClose: onClose, children: iframeContent })) : !RunOnce && (_jsxs(_Fragment, { children: [_jsx(TMLayoutWaitingContainer, { showWaitPanel: showWaitPanel, waitPanelTitle: SDKUI_Localizator.CustomButtonAction, showWaitPanelPrimary: true, waitPanelTextPrimary: waitPanelText, waitPanelValuePrimary: waitPanelValue, waitPanelMaxValuePrimary: waitPanelMaxValue, showWaitPanelSecondary: false, isCancelable: true, abortController: abortController, children: undefined }), iframeContent] }));
|
|
151
|
+
const iframeContent = (_jsxs(IframeContainer, { style: !RunOnce ? { visibility: 'hidden' } : {}, children: [loading && _jsx(LoadingOverlay, {}), error && _jsx("div", { children: "Si \u00E8 verificato un errore nel caricamento del contenuto." }), !error && _jsx(StyledIframe, { ref: iframeRef, loading: 'lazy', onLoad: handleLoad, onError: handleError, src: iframeUrl })] }));
|
|
152
|
+
return isModal && RunOnce ? (_jsx(TMModal, { title: button.title, width: isMobile ? '95%' : '60%', height: isMobile ? '95%' : '70%', resizable: isMobile ? false : true, expandable: isMobile ? false : true, onClose: onClose, children: iframeContent })) : !RunOnce && (_jsxs(_Fragment, { children: [_jsx(TMLayoutWaitingContainer, { showWaitPanel: showWaitPanel, waitPanelTitle: SDKUI_Localizator.CustomButtonAction, showWaitPanelPrimary: true, waitPanelTextPrimary: waitPanelText, waitPanelValuePrimary: waitPanelValue, waitPanelMaxValuePrimary: waitPanelMaxValue, showWaitPanelSecondary: false, isCancelable: true, abortController: abortController, children: undefined }), iframeContent] }));
|
|
116
153
|
};
|
|
117
154
|
export default TMCustomButton;
|
|
@@ -15,35 +15,7 @@ import { StyledAnimatedComponentOpacity } from '../../base/Styled';
|
|
|
15
15
|
import TMPanel from '../../base/TMPanel';
|
|
16
16
|
import TMTooltip from '../../base/TMTooltip';
|
|
17
17
|
import { ContextMenu } from '../../NewComponents/ContextMenu';
|
|
18
|
-
|
|
19
|
-
let Page = null;
|
|
20
|
-
let pdfjs = null;
|
|
21
|
-
let isPdfLibraryLoaded = false;
|
|
22
|
-
let loadingPromise = null;
|
|
23
|
-
const loadPdfLibrary = async () => {
|
|
24
|
-
if (isPdfLibraryLoaded)
|
|
25
|
-
return;
|
|
26
|
-
if (loadingPromise)
|
|
27
|
-
return loadingPromise;
|
|
28
|
-
loadingPromise = (async () => {
|
|
29
|
-
try {
|
|
30
|
-
const reactPdf = await import('react-pdf');
|
|
31
|
-
Document = reactPdf.Document;
|
|
32
|
-
Page = reactPdf.Page;
|
|
33
|
-
pdfjs = reactPdf.pdfjs;
|
|
34
|
-
pdfjs.GlobalWorkerOptions.workerSrc = `https://unpkg.com/pdfjs-dist@${pdfjs.version}/build/pdf.worker.min.mjs`;
|
|
35
|
-
isPdfLibraryLoaded = true;
|
|
36
|
-
}
|
|
37
|
-
catch (error) {
|
|
38
|
-
console.error('Failed to load react-pdf library:', error);
|
|
39
|
-
throw error;
|
|
40
|
-
}
|
|
41
|
-
finally {
|
|
42
|
-
loadingPromise = null;
|
|
43
|
-
}
|
|
44
|
-
})();
|
|
45
|
-
return loadingPromise;
|
|
46
|
-
};
|
|
18
|
+
import TMPdfViewer from '../../../helper/TMPdfViewer';
|
|
47
19
|
const ErrorContent = ({ error, isAbortError, onRetry }) => {
|
|
48
20
|
if (isAbortError) {
|
|
49
21
|
return (_jsx(StyledAnimatedComponentOpacity, { style: { width: '100%', height: '100%' }, children: _jsxs(StyledPanelStatusContainer, { children: [_jsx(IconCloseOutline, { fontSize: 92, color: TMColors.error }), _jsxs(StyledPreviewNotAvailable, { children: [_jsx("div", { children: error }), _jsx("div", { children: SDKUI_Localizator.PreviewNotAvailable })] }), _jsx(TMButton, { caption: SDKUI_Localizator.TryAgain, onClick: onRetry, showTooltip: false })] }) }));
|
|
@@ -166,53 +138,11 @@ export const TMFileViewer = ({ fileBlob, isResizingActive }) => {
|
|
|
166
138
|
const [blobUrl, setBlobUrl] = useState(undefined);
|
|
167
139
|
const [fileType, setFileType] = useState(undefined);
|
|
168
140
|
const [formattedXml, setFormattedXml] = useState(undefined);
|
|
169
|
-
const [isMobile, setIsMobile] = useState(false);
|
|
170
|
-
const [numPages, setNumPages] = useState(0);
|
|
171
|
-
const [pdfLibraryLoading, setPdfLibraryLoading] = useState(false);
|
|
172
|
-
useEffect(() => {
|
|
173
|
-
const checkIsMobile = () => {
|
|
174
|
-
const userAgent = navigator.userAgent || navigator.vendor || window.opera;
|
|
175
|
-
// Only detect actual mobile/tablet devices, NOT desktop browsers
|
|
176
|
-
const isMobileDevice =
|
|
177
|
-
// Traditional mobile detection (phones and tablets)
|
|
178
|
-
/android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini/i.test(userAgent) ||
|
|
179
|
-
// Additional Android tablet detection (covers tablets in landscape)
|
|
180
|
-
/android.*tablet|android.*mobile/i.test(userAgent) ||
|
|
181
|
-
// Touch-only devices (excludes laptops with touchscreen)
|
|
182
|
-
(('ontouchstart' in window || navigator.maxTouchPoints > 0) &&
|
|
183
|
-
!/Windows NT|Macintosh|Linux/.test(userAgent)) ||
|
|
184
|
-
// Small screen mobile devices only
|
|
185
|
-
(window.screen.width <= 768 && /Mobi|Android/i.test(userAgent));
|
|
186
|
-
setIsMobile(isMobileDevice);
|
|
187
|
-
};
|
|
188
|
-
checkIsMobile();
|
|
189
|
-
// Listen for orientation changes (important for tablets)
|
|
190
|
-
window.addEventListener('orientationchange', checkIsMobile);
|
|
191
|
-
window.addEventListener('resize', checkIsMobile);
|
|
192
|
-
return () => {
|
|
193
|
-
window.removeEventListener('orientationchange', checkIsMobile);
|
|
194
|
-
window.removeEventListener('resize', checkIsMobile);
|
|
195
|
-
};
|
|
196
|
-
}, []);
|
|
197
141
|
useEffect(() => {
|
|
198
142
|
if (fileBlob) {
|
|
199
143
|
const blobType = fileBlob.type;
|
|
200
144
|
setFileType(blobType);
|
|
201
145
|
setFormattedXml(undefined);
|
|
202
|
-
// Load PDF library immediately if it's a PDF file on mobile
|
|
203
|
-
if (isMobile && blobType === 'application/pdf' && !isPdfLibraryLoaded && !pdfLibraryLoading) {
|
|
204
|
-
setPdfLibraryLoading(true);
|
|
205
|
-
loadPdfLibrary()
|
|
206
|
-
.then(() => {
|
|
207
|
-
console.log('PDF library loaded successfully');
|
|
208
|
-
})
|
|
209
|
-
.catch((error) => {
|
|
210
|
-
console.error('Failed to load PDF library:', error);
|
|
211
|
-
})
|
|
212
|
-
.finally(() => {
|
|
213
|
-
setPdfLibraryLoading(false);
|
|
214
|
-
});
|
|
215
|
-
}
|
|
216
146
|
const fileName = fileBlob.name || '';
|
|
217
147
|
const fileExtension = fileName.split('.').pop()?.toLowerCase() || '';
|
|
218
148
|
const isConfigFile = ['config', 'cfg'].includes(fileExtension);
|
|
@@ -269,36 +199,10 @@ export const TMFileViewer = ({ fileBlob, isResizingActive }) => {
|
|
|
269
199
|
if (fileBlob.type.includes('image')) {
|
|
270
200
|
return (_jsx(ImageViewer, { fileBlob: fileBlob, alt: '' }));
|
|
271
201
|
}
|
|
272
|
-
if (fileType === 'application/pdf'
|
|
273
|
-
|
|
274
|
-
return (_jsxs("div", { style: {
|
|
275
|
-
display: 'flex',
|
|
276
|
-
justifyContent: 'center',
|
|
277
|
-
alignItems: 'center',
|
|
278
|
-
height: '100%',
|
|
279
|
-
flexDirection: 'column',
|
|
280
|
-
gap: '10px'
|
|
281
|
-
}, children: [_jsx(IconPreview, { fontSize: 64 }), _jsxs("div", { children: [SDKUI_Localizator.Loading, "..."] })] }));
|
|
282
|
-
}
|
|
283
|
-
return (_jsx(PDFViewerContainer, { children: _jsx(Document, { file: blobUrl, onLoadSuccess: ({ numPages }) => setNumPages(numPages), loading: _jsxs("div", { style: {
|
|
284
|
-
display: 'flex',
|
|
285
|
-
justifyContent: 'center',
|
|
286
|
-
alignItems: 'center',
|
|
287
|
-
height: '100%',
|
|
288
|
-
flexDirection: 'column',
|
|
289
|
-
gap: '10px'
|
|
290
|
-
}, children: [_jsx(IconPreview, { fontSize: 64 }), _jsxs("div", { children: [SDKUI_Localizator.Loading, "..."] })] }), error: _jsxs("div", { style: {
|
|
291
|
-
display: 'flex',
|
|
292
|
-
justifyContent: 'center',
|
|
293
|
-
alignItems: 'center',
|
|
294
|
-
height: '100%',
|
|
295
|
-
flexDirection: 'column',
|
|
296
|
-
gap: '10px'
|
|
297
|
-
}, children: [_jsx(IconCloseOutline, { fontSize: 64, color: TMColors.error }), _jsx("div", { children: "Errore nel caricamento del PDF" })] }), children: Array.from(new Array(numPages), (el, index) => (_jsx(Page, { pageNumber: index + 1, renderTextLayer: false, renderAnnotationLayer: false, width: window.innerWidth }, `page_${index + 1}`))) }) }));
|
|
202
|
+
if (fileType === 'application/pdf') {
|
|
203
|
+
return _jsx(TMPdfViewer, { pdfBlob: fileBlob, isResizingActive: isResizingActive, enableFitToWidth: true });
|
|
298
204
|
}
|
|
299
|
-
return (_jsx("iframe", { srcDoc: formattedXml ? `<html><body>${formattedXml}</body></html>` : undefined, src: !formattedXml
|
|
300
|
-
? (fileType === 'application/pdf' ? `${blobUrl}#view=FitH&scrollbar=1` : blobUrl)
|
|
301
|
-
: undefined, title: "File Viewer", width: "100%", height: "100%", style: { border: 'none', zIndex: 0, pointerEvents: isResizingActive === true ? "none" : "auto" } }, blobUrl));
|
|
205
|
+
return (_jsx("iframe", { srcDoc: formattedXml ? `<html><body>${formattedXml}</body></html>` : undefined, src: !formattedXml ? blobUrl : undefined, title: "File Viewer", width: "100%", height: "100%", style: { border: 'none', zIndex: 0, pointerEvents: isResizingActive === true ? "none" : "auto" } }, blobUrl));
|
|
302
206
|
};
|
|
303
207
|
const ImageViewer = ({ fileBlob, alt = 'Image', className }) => {
|
|
304
208
|
const containerRef = useRef(null);
|
|
@@ -360,7 +360,7 @@ const TMSearchResult = ({ allTasks = [], getAllTasks, deleteTaskByIdsCallback, a
|
|
|
360
360
|
const customButtonMenuItems = () => {
|
|
361
361
|
const customButtonsItems = customButtonsLayout?.customButtons?.filter((customButton) => customButton.isForSearchResult && customButton.isForSearchResult > 0)
|
|
362
362
|
.map((customButton) => ({
|
|
363
|
-
icon: svgToString(TMImageLibrary({ imageID: customButton.glyphID, showPath:
|
|
363
|
+
icon: svgToString(TMImageLibrary({ imageID: customButton.glyphID, showPath: true })),
|
|
364
364
|
text: customButton.title || 'Bottone personalizzato',
|
|
365
365
|
onClick: () => setCustomButton(customButton)
|
|
366
366
|
}));
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useState, useRef, useCallback } from "react";
|
|
3
|
+
import { pdfjs, Document, Page } from 'react-pdf';
|
|
4
|
+
import styled from "styled-components";
|
|
5
|
+
import { LoadIndicator } from 'devextreme-react/load-indicator';
|
|
6
|
+
import { IconCloseOutline } from "./TMIcons";
|
|
7
|
+
import { SDKUI_Localizator } from "./SDKUI_Localizator";
|
|
8
|
+
import { TMColors } from "../utils/theme";
|
|
9
|
+
pdfjs.GlobalWorkerOptions.workerSrc = new URL('pdfjs-dist/build/pdf.worker.min.mjs', import.meta.url).toString();
|
|
10
|
+
const PDFViewerContainer = styled.div `
|
|
11
|
+
width: 100%;
|
|
12
|
+
height: 100%;
|
|
13
|
+
overflow-y: auto;
|
|
14
|
+
overflow-x: hidden;
|
|
15
|
+
background-color: #f5f5f5;
|
|
16
|
+
position: relative;
|
|
17
|
+
|
|
18
|
+
.react-pdf__Document {
|
|
19
|
+
display: flex;
|
|
20
|
+
flex-direction: column;
|
|
21
|
+
align-items: center;
|
|
22
|
+
gap: 10px;
|
|
23
|
+
padding: 10px 0;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.react-pdf__Page {
|
|
27
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
28
|
+
margin: 0 auto;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.react-pdf__Page__canvas {
|
|
32
|
+
max-width: 100%;
|
|
33
|
+
height: auto !important;
|
|
34
|
+
}
|
|
35
|
+
`;
|
|
36
|
+
const LoadingOverlay = styled.div `
|
|
37
|
+
position: absolute;
|
|
38
|
+
top: 0;
|
|
39
|
+
left: 0;
|
|
40
|
+
width: 100%;
|
|
41
|
+
height: 100%;
|
|
42
|
+
background-color: rgba(245, 245, 245, 0.95);
|
|
43
|
+
display: flex;
|
|
44
|
+
justify-content: center;
|
|
45
|
+
align-items: center;
|
|
46
|
+
z-index: 1000;
|
|
47
|
+
`;
|
|
48
|
+
const TMPdfViewer = (props) => {
|
|
49
|
+
const { pdfBlob, title = "Anteprima PDF", isResizingActive, enableFitToWidth = false } = props;
|
|
50
|
+
const [isMobile, setIsMobile] = useState(false);
|
|
51
|
+
const [totalPagesNumber, setTotalPagesNumber] = useState(0);
|
|
52
|
+
const [loadedPagesNumber, setLoadedPagesNumber] = useState(0);
|
|
53
|
+
const [visiblePages, setVisiblePages] = useState(new Set([1]));
|
|
54
|
+
const [pdfUrl, setPdfUrl] = useState("");
|
|
55
|
+
const observerRef = useRef(null);
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
const checkIsMobile = () => {
|
|
58
|
+
const userAgent = navigator.userAgent || navigator.vendor || window.opera;
|
|
59
|
+
// Detect actual mobile/tablet devices
|
|
60
|
+
const isMobileDevice =
|
|
61
|
+
// User agent detection (phones and tablets)
|
|
62
|
+
/android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini/i.test(userAgent) ||
|
|
63
|
+
// Media query for touch devices with coarse pointer (more reliable than screen width)
|
|
64
|
+
(window.matchMedia?.('(hover: none) and (pointer: coarse)').matches ?? false) ||
|
|
65
|
+
// Touch-capable devices excluding desktop OS
|
|
66
|
+
(navigator.maxTouchPoints > 1 && !/Win|Mac|Linux x86_64/i.test(userAgent));
|
|
67
|
+
setIsMobile(isMobileDevice);
|
|
68
|
+
};
|
|
69
|
+
checkIsMobile();
|
|
70
|
+
// Create URL for iframe
|
|
71
|
+
const url = URL.createObjectURL(pdfBlob);
|
|
72
|
+
setPdfUrl(url);
|
|
73
|
+
return () => {
|
|
74
|
+
if (url) {
|
|
75
|
+
URL.revokeObjectURL(url);
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
}, [pdfBlob]);
|
|
79
|
+
/**
|
|
80
|
+
* Callback per l'Intersection Observer che gestisce il lazy loading delle pagine PDF.
|
|
81
|
+
* Viene chiamato ogni volta che una pagina entra o esce dal viewport (area visibile).
|
|
82
|
+
*
|
|
83
|
+
* - Monitora quando gli elementi (pagine) diventano visibili
|
|
84
|
+
* - Aggiunge il numero di pagina al Set delle pagine visibili quando entra nel viewport
|
|
85
|
+
* - Il rootMargin di 500px carica le pagine prima che siano effettivamente visibili (preloading)
|
|
86
|
+
*/
|
|
87
|
+
const pageObserverCallback = useCallback((entries) => {
|
|
88
|
+
entries.forEach(entry => {
|
|
89
|
+
const pageNum = parseInt(entry.target.getAttribute('data-page-number') || '0');
|
|
90
|
+
if (entry.isIntersecting) {
|
|
91
|
+
// Aggiunge la pagina al Set di quelle da renderizzare
|
|
92
|
+
setVisiblePages(prev => new Set([...prev, pageNum]));
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
}, []);
|
|
96
|
+
/**
|
|
97
|
+
* Crea e configura l'Intersection Observer per il lazy loading.
|
|
98
|
+
*
|
|
99
|
+
* Configurazione:
|
|
100
|
+
* - root: null → osserva rispetto al viewport del browser
|
|
101
|
+
* - rootMargin: '500px' → inizia il caricamento 500px prima che la pagina sia visibile (buffer)
|
|
102
|
+
* - threshold: 0.01 → trigger quando anche solo l'1% dell'elemento è visibile
|
|
103
|
+
*
|
|
104
|
+
* Benefici:
|
|
105
|
+
* - Non blocca l'UI durante il rendering
|
|
106
|
+
* - Carica solo le pagine necessarie
|
|
107
|
+
* - Migliora le performance su PDF con molte pagine
|
|
108
|
+
*/
|
|
109
|
+
useEffect(() => {
|
|
110
|
+
observerRef.current = new IntersectionObserver(pageObserverCallback, {
|
|
111
|
+
root: null,
|
|
112
|
+
rootMargin: '500px',
|
|
113
|
+
threshold: 0.01
|
|
114
|
+
});
|
|
115
|
+
return () => {
|
|
116
|
+
// Cleanup: disconnette l'observer quando il componente viene smontato
|
|
117
|
+
observerRef.current?.disconnect();
|
|
118
|
+
};
|
|
119
|
+
}, [pageObserverCallback]);
|
|
120
|
+
// Use iframe for desktop, react-pdf for mobile
|
|
121
|
+
if (!isMobile && pdfUrl) {
|
|
122
|
+
return (_jsx(PDFViewerContainer, { children: _jsx("iframe", { src: `${pdfUrl}#${enableFitToWidth ? 'view=FitH&' : ''}scrollbar=1`, title: title, style: {
|
|
123
|
+
width: '100%',
|
|
124
|
+
height: '100%',
|
|
125
|
+
border: 'none',
|
|
126
|
+
zIndex: 0,
|
|
127
|
+
pointerEvents: isResizingActive === true ? "none" : "auto"
|
|
128
|
+
} }, pdfUrl) }));
|
|
129
|
+
}
|
|
130
|
+
return _jsxs(PDFViewerContainer, { children: [loadedPagesNumber === 0 && totalPagesNumber > 0 && _jsx(LoadingOverlay, { children: _jsxs("div", { style: {
|
|
131
|
+
display: 'flex',
|
|
132
|
+
justifyContent: 'center',
|
|
133
|
+
alignItems: 'center',
|
|
134
|
+
flexDirection: 'column',
|
|
135
|
+
gap: '10px'
|
|
136
|
+
}, children: [_jsx(LoadIndicator, { height: 60, width: 60 }), _jsxs("div", { children: [SDKUI_Localizator.Loading, "..."] })] }) }), _jsx("div", { style: { display: loadedPagesNumber > 0 && totalPagesNumber > 0 ? 'block' : 'none' }, children: _jsx(Document, { file: pdfBlob, onLoadSuccess: ({ numPages }) => {
|
|
137
|
+
setTotalPagesNumber(numPages);
|
|
138
|
+
setLoadedPagesNumber(0);
|
|
139
|
+
}, loading: _jsxs("div", { style: {
|
|
140
|
+
display: 'flex',
|
|
141
|
+
justifyContent: 'center',
|
|
142
|
+
alignItems: 'center',
|
|
143
|
+
height: '100%',
|
|
144
|
+
flexDirection: 'column',
|
|
145
|
+
gap: '10px'
|
|
146
|
+
}, children: [_jsx(LoadIndicator, { height: 60, width: 60 }), _jsxs("div", { children: [SDKUI_Localizator.Loading, "..."] })] }), error: _jsxs("div", { style: {
|
|
147
|
+
display: 'flex',
|
|
148
|
+
justifyContent: 'center',
|
|
149
|
+
alignItems: 'center',
|
|
150
|
+
height: '100%',
|
|
151
|
+
flexDirection: 'column',
|
|
152
|
+
gap: '10px'
|
|
153
|
+
}, children: [_jsx(IconCloseOutline, { fontSize: 64, color: TMColors.error }), _jsx("div", { children: "Errore nel caricamento del PDF" })] }), children: Array.from(new Array(totalPagesNumber), (el, index) => {
|
|
154
|
+
const pageNumber = index + 1;
|
|
155
|
+
const shouldRender = visiblePages.has(pageNumber);
|
|
156
|
+
return (_jsx("div", { "data-page-number": pageNumber, ref: (el) => {
|
|
157
|
+
if (el && observerRef.current) {
|
|
158
|
+
observerRef.current.observe(el);
|
|
159
|
+
}
|
|
160
|
+
}, style: {
|
|
161
|
+
minHeight: shouldRender ? 'auto' : '1000px',
|
|
162
|
+
display: 'flex',
|
|
163
|
+
justifyContent: 'center',
|
|
164
|
+
alignItems: 'center'
|
|
165
|
+
}, children: shouldRender && (_jsx(Page, { pageNumber: pageNumber, renderTextLayer: false, renderAnnotationLayer: false, width: Math.min(window.innerWidth - 40, 1200), loading: _jsx("div", { style: { padding: '20px' }, children: _jsx(LoadIndicator, { height: 40, width: 40 }) }), onLoadSuccess: () => {
|
|
166
|
+
setLoadedPagesNumber(prev => prev + 1);
|
|
167
|
+
} })) }, `page_${pageNumber}`));
|
|
168
|
+
}) }) })] });
|
|
169
|
+
};
|
|
170
|
+
export default TMPdfViewer;
|
package/lib/helper/index.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ export * from './queryHelper';
|
|
|
9
9
|
export * from './TMUtils';
|
|
10
10
|
export * from './TMCommandsContextMenu';
|
|
11
11
|
export * from './TMConditionalWrapper';
|
|
12
|
+
export * from './TMPdfViewer';
|
|
12
13
|
export * from './TMToppyMessage';
|
|
13
14
|
export * from './GlobalStyles';
|
|
14
15
|
export * from './checkinCheckoutManager';
|
package/lib/helper/index.js
CHANGED
|
@@ -9,6 +9,7 @@ export * from './queryHelper';
|
|
|
9
9
|
export * from './TMUtils';
|
|
10
10
|
export * from './TMCommandsContextMenu';
|
|
11
11
|
export * from './TMConditionalWrapper';
|
|
12
|
+
export * from './TMPdfViewer';
|
|
12
13
|
export * from './TMToppyMessage';
|
|
13
14
|
export * from './GlobalStyles';
|
|
14
15
|
export * from './checkinCheckoutManager';
|
package/package.json
CHANGED
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useState, useEffect, useRef } from 'react';
|
|
3
|
-
import * as S from './styles';
|
|
4
|
-
const Notification = ({ title, message, mode = 'info', position = 'top-right', duration = 3000, closable = false, stopOnMouseEnter = true, hasProgress = true, onClose, }) => {
|
|
5
|
-
const [state, setState] = useState({
|
|
6
|
-
visible: true,
|
|
7
|
-
isPaused: false,
|
|
8
|
-
progress: 100,
|
|
9
|
-
});
|
|
10
|
-
const timeoutRef = useRef(undefined);
|
|
11
|
-
const remainingTimeRef = useRef(duration);
|
|
12
|
-
const pauseTimeRef = useRef(0);
|
|
13
|
-
const closeNotification = () => {
|
|
14
|
-
setState(prev => ({ ...prev, visible: false }));
|
|
15
|
-
setTimeout(() => {
|
|
16
|
-
onClose?.();
|
|
17
|
-
}, 300);
|
|
18
|
-
};
|
|
19
|
-
useEffect(() => {
|
|
20
|
-
// Set up auto-close timer
|
|
21
|
-
timeoutRef.current = setTimeout(() => {
|
|
22
|
-
closeNotification();
|
|
23
|
-
}, duration);
|
|
24
|
-
return () => {
|
|
25
|
-
if (timeoutRef.current) {
|
|
26
|
-
clearTimeout(timeoutRef.current);
|
|
27
|
-
}
|
|
28
|
-
};
|
|
29
|
-
}, [duration]);
|
|
30
|
-
const handleMouseEnter = () => {
|
|
31
|
-
if (!stopOnMouseEnter)
|
|
32
|
-
return;
|
|
33
|
-
// Pause the timer
|
|
34
|
-
if (timeoutRef.current) {
|
|
35
|
-
clearTimeout(timeoutRef.current);
|
|
36
|
-
pauseTimeRef.current = Date.now();
|
|
37
|
-
}
|
|
38
|
-
setState(prev => ({ ...prev, isPaused: true }));
|
|
39
|
-
};
|
|
40
|
-
const handleMouseLeave = () => {
|
|
41
|
-
if (!stopOnMouseEnter)
|
|
42
|
-
return;
|
|
43
|
-
// Resume the timer with remaining time
|
|
44
|
-
const pauseDuration = Date.now() - pauseTimeRef.current;
|
|
45
|
-
remainingTimeRef.current = Math.max(0, remainingTimeRef.current - pauseDuration);
|
|
46
|
-
timeoutRef.current = setTimeout(() => {
|
|
47
|
-
closeNotification();
|
|
48
|
-
}, remainingTimeRef.current);
|
|
49
|
-
setState(prev => ({ ...prev, isPaused: false }));
|
|
50
|
-
};
|
|
51
|
-
const handleClose = (e) => {
|
|
52
|
-
e.stopPropagation();
|
|
53
|
-
if (timeoutRef.current) {
|
|
54
|
-
clearTimeout(timeoutRef.current);
|
|
55
|
-
}
|
|
56
|
-
closeNotification();
|
|
57
|
-
};
|
|
58
|
-
return (_jsxs(S.NotificationContainer, { "$position": position, "$mode": mode, "$visible": state.visible, onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, role: "alert", "aria-live": "assertive", children: [_jsxs(S.NotificationContent, { children: [_jsx(S.NotificationTitle, { children: title }), _jsx(S.NotificationMessage, { children: message })] }), closable && (_jsx(S.CloseButton, { onClick: handleClose, "aria-label": "Close notification", children: "\u00D7" })), hasProgress && (_jsx(S.ProgressBar, { "$duration": duration, "$mode": mode, "$isPaused": state.isPaused }))] }));
|
|
59
|
-
};
|
|
60
|
-
export default Notification;
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import type { NotificationPosition } from './types';
|
|
3
|
-
interface NotificationContainerProps {
|
|
4
|
-
position: NotificationPosition;
|
|
5
|
-
children: React.ReactNode;
|
|
6
|
-
}
|
|
7
|
-
declare const NotificationContainer: React.FC<NotificationContainerProps>;
|
|
8
|
-
export default NotificationContainer;
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import styled from 'styled-components';
|
|
3
|
-
const Container = styled.div `
|
|
4
|
-
position: fixed;
|
|
5
|
-
z-index: 10002;
|
|
6
|
-
display: flex;
|
|
7
|
-
flex-direction: column;
|
|
8
|
-
gap: 12px;
|
|
9
|
-
pointer-events: none;
|
|
10
|
-
|
|
11
|
-
${props => {
|
|
12
|
-
const isTop = props.$position.startsWith('top');
|
|
13
|
-
const isBottom = props.$position.startsWith('bottom');
|
|
14
|
-
const isLeft = props.$position.endsWith('left');
|
|
15
|
-
const isRight = props.$position.endsWith('right');
|
|
16
|
-
const isCenter = props.$position.endsWith('center');
|
|
17
|
-
return `
|
|
18
|
-
${isTop ? 'top: 24px;' : ''}
|
|
19
|
-
${isBottom ? 'bottom: 24px;' : ''}
|
|
20
|
-
${isLeft ? 'left: 24px;' : ''}
|
|
21
|
-
${isRight ? 'right: 24px;' : ''}
|
|
22
|
-
${isCenter ? 'left: 50%; transform: translateX(-50%);' : ''}
|
|
23
|
-
`;
|
|
24
|
-
}}
|
|
25
|
-
|
|
26
|
-
& > * {
|
|
27
|
-
pointer-events: auto;
|
|
28
|
-
}
|
|
29
|
-
`;
|
|
30
|
-
const NotificationContainer = ({ position, children }) => {
|
|
31
|
-
return _jsx(Container, { "$position": position, children: children });
|
|
32
|
-
};
|
|
33
|
-
export default NotificationContainer;
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import type { NotificationMode, NotificationPosition } from './types';
|
|
2
|
-
export declare const NotificationContainer: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components/dist/types").Substitute<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, {
|
|
3
|
-
$position: NotificationPosition;
|
|
4
|
-
$mode: NotificationMode;
|
|
5
|
-
$visible: boolean;
|
|
6
|
-
}>> & string;
|
|
7
|
-
export declare const NotificationContent: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never>> & string;
|
|
8
|
-
export declare const NotificationTitle: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never>> & string;
|
|
9
|
-
export declare const NotificationMessage: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never>> & string;
|
|
10
|
-
export declare const CloseButton: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<import("react").DetailedHTMLProps<import("react").ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>, never>> & string;
|
|
11
|
-
export declare const ProgressBar: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components/dist/types").Substitute<import("styled-components/dist/types").Substitute<import("styled-components/dist/types").Substitute<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, Omit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & {
|
|
12
|
-
ref?: ((instance: HTMLDivElement | null) => void | import("react").DO_NOT_USE_OR_YOU_WILL_BE_FIRED_CALLBACK_REF_RETURN_VALUES[keyof import("react").DO_NOT_USE_OR_YOU_WILL_BE_FIRED_CALLBACK_REF_RETURN_VALUES]) | import("react").RefObject<HTMLDivElement> | null | undefined;
|
|
13
|
-
}>, {
|
|
14
|
-
$duration: number;
|
|
15
|
-
$mode: NotificationMode;
|
|
16
|
-
$isPaused: boolean;
|
|
17
|
-
}>, {
|
|
18
|
-
$duration: number;
|
|
19
|
-
$mode: NotificationMode;
|
|
20
|
-
$isPaused: boolean;
|
|
21
|
-
}>> & string;
|
|
@@ -1,180 +0,0 @@
|
|
|
1
|
-
import styled, { keyframes } from 'styled-components';
|
|
2
|
-
const slideInFromTop = keyframes `
|
|
3
|
-
from {
|
|
4
|
-
opacity: 0;
|
|
5
|
-
transform: translateY(-100%);
|
|
6
|
-
}
|
|
7
|
-
to {
|
|
8
|
-
opacity: 1;
|
|
9
|
-
transform: translateY(0);
|
|
10
|
-
}
|
|
11
|
-
`;
|
|
12
|
-
const slideInFromBottom = keyframes `
|
|
13
|
-
from {
|
|
14
|
-
opacity: 0;
|
|
15
|
-
transform: translateY(100%);
|
|
16
|
-
}
|
|
17
|
-
to {
|
|
18
|
-
opacity: 1;
|
|
19
|
-
transform: translateY(0);
|
|
20
|
-
}
|
|
21
|
-
`;
|
|
22
|
-
const slideOut = keyframes `
|
|
23
|
-
from {
|
|
24
|
-
opacity: 1;
|
|
25
|
-
transform: scale(1);
|
|
26
|
-
}
|
|
27
|
-
to {
|
|
28
|
-
opacity: 0;
|
|
29
|
-
transform: scale(0.9);
|
|
30
|
-
}
|
|
31
|
-
`;
|
|
32
|
-
const getModeColors = (mode) => {
|
|
33
|
-
const colors = {
|
|
34
|
-
success: {
|
|
35
|
-
bg: '#10b981',
|
|
36
|
-
bgDark: '#059669',
|
|
37
|
-
border: '#34d399',
|
|
38
|
-
text: '#ffffff',
|
|
39
|
-
},
|
|
40
|
-
error: {
|
|
41
|
-
bg: '#ef4444',
|
|
42
|
-
bgDark: '#dc2626',
|
|
43
|
-
border: '#f87171',
|
|
44
|
-
text: '#ffffff',
|
|
45
|
-
},
|
|
46
|
-
warning: {
|
|
47
|
-
bg: '#f59e0b',
|
|
48
|
-
bgDark: '#d97706',
|
|
49
|
-
border: '#fbbf24',
|
|
50
|
-
text: '#ffffff',
|
|
51
|
-
},
|
|
52
|
-
info: {
|
|
53
|
-
bg: '#3b82f6',
|
|
54
|
-
bgDark: '#2563eb',
|
|
55
|
-
border: '#60a5fa',
|
|
56
|
-
text: '#ffffff',
|
|
57
|
-
},
|
|
58
|
-
};
|
|
59
|
-
return colors[mode];
|
|
60
|
-
};
|
|
61
|
-
export const NotificationContainer = styled.div `
|
|
62
|
-
position: relative;
|
|
63
|
-
z-index: 1;
|
|
64
|
-
min-width: 320px;
|
|
65
|
-
max-width: 420px;
|
|
66
|
-
background: ${props => getModeColors(props.$mode).bg};
|
|
67
|
-
border-radius: 12px;
|
|
68
|
-
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.2),
|
|
69
|
-
0 4px 12px rgba(0, 0, 0, 0.15);
|
|
70
|
-
padding: 16px 20px;
|
|
71
|
-
animation: ${props => {
|
|
72
|
-
if (!props.$visible)
|
|
73
|
-
return slideOut;
|
|
74
|
-
return props.$position.startsWith('top') ? slideInFromTop : slideInFromBottom;
|
|
75
|
-
}} 0.3s cubic-bezier(0.4, 0, 0.2, 1) forwards;
|
|
76
|
-
backdrop-filter: blur(10px);
|
|
77
|
-
border: 2px solid ${props => getModeColors(props.$mode).border};
|
|
78
|
-
color: ${props => getModeColors(props.$mode).text};
|
|
79
|
-
overflow: hidden;
|
|
80
|
-
|
|
81
|
-
[data-theme='dark'] & {
|
|
82
|
-
background: ${props => getModeColors(props.$mode).bgDark};
|
|
83
|
-
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.4),
|
|
84
|
-
0 4px 12px rgba(0, 0, 0, 0.3);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
@media (max-width: 768px) {
|
|
88
|
-
min-width: 280px;
|
|
89
|
-
max-width: calc(100vw - 48px);
|
|
90
|
-
}
|
|
91
|
-
`;
|
|
92
|
-
export const NotificationContent = styled.div `
|
|
93
|
-
display: flex;
|
|
94
|
-
flex-direction: column;
|
|
95
|
-
gap: 6px;
|
|
96
|
-
padding-right: 24px;
|
|
97
|
-
`;
|
|
98
|
-
export const NotificationTitle = styled.div `
|
|
99
|
-
font-size: 16px;
|
|
100
|
-
font-weight: 600;
|
|
101
|
-
line-height: 1.4;
|
|
102
|
-
letter-spacing: -0.01em;
|
|
103
|
-
`;
|
|
104
|
-
export const NotificationMessage = styled.div `
|
|
105
|
-
font-size: 14px;
|
|
106
|
-
font-weight: 400;
|
|
107
|
-
line-height: 1.5;
|
|
108
|
-
opacity: 0.95;
|
|
109
|
-
`;
|
|
110
|
-
export const CloseButton = styled.button `
|
|
111
|
-
position: absolute;
|
|
112
|
-
top: 12px;
|
|
113
|
-
right: 12px;
|
|
114
|
-
background: transparent;
|
|
115
|
-
border: none;
|
|
116
|
-
color: inherit;
|
|
117
|
-
cursor: pointer;
|
|
118
|
-
width: 24px;
|
|
119
|
-
height: 24px;
|
|
120
|
-
display: flex;
|
|
121
|
-
align-items: center;
|
|
122
|
-
justify-content: center;
|
|
123
|
-
border-radius: 6px;
|
|
124
|
-
transition: all 0.15s ease;
|
|
125
|
-
font-size: 18px;
|
|
126
|
-
line-height: 1;
|
|
127
|
-
padding: 0;
|
|
128
|
-
opacity: 0.8;
|
|
129
|
-
|
|
130
|
-
&:hover {
|
|
131
|
-
opacity: 1;
|
|
132
|
-
background: rgba(255, 255, 255, 0.2);
|
|
133
|
-
transform: scale(1.1);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
&:active {
|
|
137
|
-
transform: scale(0.95);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
&:focus {
|
|
141
|
-
outline: 2px solid rgba(255, 255, 255, 0.5);
|
|
142
|
-
outline-offset: 2px;
|
|
143
|
-
}
|
|
144
|
-
`;
|
|
145
|
-
export const ProgressBar = styled.div.attrs(props => ({
|
|
146
|
-
style: {
|
|
147
|
-
animationDuration: `${props.$duration}ms`,
|
|
148
|
-
},
|
|
149
|
-
})) `
|
|
150
|
-
position: absolute;
|
|
151
|
-
bottom: 0;
|
|
152
|
-
left: 0;
|
|
153
|
-
height: 4px;
|
|
154
|
-
width: 100%;
|
|
155
|
-
background: ${props => getModeColors(props.$mode).border};
|
|
156
|
-
border-radius: 0 0 0 10px;
|
|
157
|
-
box-shadow: 0 0 8px ${props => getModeColors(props.$mode).border};
|
|
158
|
-
transform-origin: left;
|
|
159
|
-
animation: progress-shrink linear forwards;
|
|
160
|
-
animation-play-state: ${props => props.$isPaused ? 'paused' : 'running'};
|
|
161
|
-
|
|
162
|
-
@keyframes progress-shrink {
|
|
163
|
-
from {
|
|
164
|
-
transform: scaleX(1);
|
|
165
|
-
}
|
|
166
|
-
to {
|
|
167
|
-
transform: scaleX(0);
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
&::after {
|
|
172
|
-
content: '';
|
|
173
|
-
position: absolute;
|
|
174
|
-
top: 0;
|
|
175
|
-
right: 0;
|
|
176
|
-
width: 20px;
|
|
177
|
-
height: 100%;
|
|
178
|
-
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.4));
|
|
179
|
-
}
|
|
180
|
-
`;
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
export type NotificationMode = 'warning' | 'info' | 'error' | 'success';
|
|
2
|
-
export type NotificationPosition = 'top-left' | 'top-center' | 'top-right' | 'bottom-left' | 'bottom-center' | 'bottom-right';
|
|
3
|
-
export interface NotificationProps {
|
|
4
|
-
title: string;
|
|
5
|
-
message: string;
|
|
6
|
-
mode?: NotificationMode;
|
|
7
|
-
position?: NotificationPosition;
|
|
8
|
-
duration?: number;
|
|
9
|
-
closable?: boolean;
|
|
10
|
-
stopOnMouseEnter?: boolean;
|
|
11
|
-
hasProgress?: boolean;
|
|
12
|
-
onClose?: () => void;
|
|
13
|
-
}
|
|
14
|
-
export interface NotificationState {
|
|
15
|
-
visible: boolean;
|
|
16
|
-
isPaused: boolean;
|
|
17
|
-
progress: number;
|
|
18
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|