ml-ui-lib 1.0.47 → 1.0.48
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.
|
@@ -119,10 +119,10 @@
|
|
|
119
119
|
.spb-header {
|
|
120
120
|
display: flex;
|
|
121
121
|
align-items: center;
|
|
122
|
-
justify-content:
|
|
122
|
+
justify-content: end;
|
|
123
123
|
padding: 12px 16px;
|
|
124
|
-
border-bottom: 1px solid #f1f1f1;
|
|
125
|
-
min-height:
|
|
124
|
+
/* border-bottom: 1px solid #f1f1f1; */
|
|
125
|
+
min-height: 0px;
|
|
126
126
|
}
|
|
127
127
|
|
|
128
128
|
.spb-title {
|
|
@@ -136,19 +136,30 @@
|
|
|
136
136
|
padding: 16px;
|
|
137
137
|
overflow-y: auto;
|
|
138
138
|
flex: 1 1 auto;
|
|
139
|
+
|
|
140
|
+
/* ✅ hide scrollbar (Firefox) */
|
|
141
|
+
scrollbar-width: none;
|
|
142
|
+
|
|
143
|
+
/* ✅ prevent layout shift on some browsers */
|
|
144
|
+
-ms-overflow-style: none;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/* ✅ Chrome, Safari, Edge */
|
|
148
|
+
.spb-body::-webkit-scrollbar {
|
|
149
|
+
display: none;
|
|
139
150
|
}
|
|
140
151
|
|
|
141
152
|
/* Desktop close button */
|
|
142
153
|
.spb-close-btn {
|
|
143
154
|
background: none;
|
|
144
155
|
border: none;
|
|
145
|
-
font-size:
|
|
156
|
+
font-size: 25px;
|
|
146
157
|
line-height: 1;
|
|
147
158
|
cursor: pointer;
|
|
148
|
-
padding:
|
|
159
|
+
padding: 30px 0px 25px 10px;
|
|
149
160
|
border-radius: 6px;
|
|
150
161
|
color: #111827;
|
|
151
|
-
margin-right:
|
|
162
|
+
margin-right: 0px;
|
|
152
163
|
}
|
|
153
164
|
|
|
154
165
|
.spb-close-btn:hover {
|
|
@@ -188,9 +199,9 @@
|
|
|
188
199
|
cursor: grabbing;
|
|
189
200
|
}
|
|
190
201
|
|
|
191
|
-
.spb-close-btn {
|
|
202
|
+
/* .spb-close-btn {
|
|
192
203
|
display: none;
|
|
193
|
-
}
|
|
204
|
+
} */
|
|
194
205
|
|
|
195
206
|
/* ✅ Mobile top-left close */
|
|
196
207
|
.spb-mobile-close-btn {
|
|
@@ -2,13 +2,17 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useEffect, useRef, useState } from "react";
|
|
4
4
|
import "./SlidingPanel.css";
|
|
5
|
-
export const SlidingPanel = ({ isOpen, onClose, children, height = "80%", width = "100%",
|
|
5
|
+
export const SlidingPanel = ({ isOpen, onClose, children, height = "80%", width = "100%", closeOnOverlayClick = true, position = "bottom", }) => {
|
|
6
6
|
const [visible, setVisible] = useState(false);
|
|
7
7
|
const [animateOpen, setAnimateOpen] = useState(false);
|
|
8
8
|
const panelRef = useRef(null);
|
|
9
9
|
const headerRef = useRef(null);
|
|
10
10
|
const handleRef = useRef(null);
|
|
11
|
-
|
|
11
|
+
let startPos = 0;
|
|
12
|
+
let currentPos = 0;
|
|
13
|
+
let isDragging = false;
|
|
14
|
+
const isVertical = position === "bottom" || position === "top";
|
|
15
|
+
// OPEN / CLOSE animation
|
|
12
16
|
useEffect(() => {
|
|
13
17
|
if (isOpen) {
|
|
14
18
|
setVisible(true);
|
|
@@ -17,7 +21,6 @@ export const SlidingPanel = ({ isOpen, onClose, children, height = "80%", width
|
|
|
17
21
|
return () => clearTimeout(id);
|
|
18
22
|
}
|
|
19
23
|
else {
|
|
20
|
-
// Slide out according to position
|
|
21
24
|
if (panelRef.current) {
|
|
22
25
|
switch (position) {
|
|
23
26
|
case "bottom":
|
|
@@ -35,29 +38,38 @@ export const SlidingPanel = ({ isOpen, onClose, children, height = "80%", width
|
|
|
35
38
|
}
|
|
36
39
|
}
|
|
37
40
|
setAnimateOpen(false);
|
|
38
|
-
// Keep in DOM until slide completes
|
|
39
41
|
const id = setTimeout(() => setVisible(false), 320);
|
|
40
42
|
document.body.style.overflow = "";
|
|
41
43
|
return () => clearTimeout(id);
|
|
42
44
|
}
|
|
43
45
|
}, [isOpen, position]);
|
|
44
|
-
//
|
|
46
|
+
// DRAG LOGIC
|
|
45
47
|
useEffect(() => {
|
|
46
48
|
const panel = panelRef.current;
|
|
47
49
|
const handle = handleRef.current;
|
|
48
50
|
const header = headerRef.current;
|
|
49
51
|
if (!panel)
|
|
50
52
|
return;
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
53
|
+
// ✅ CHANGE 1: whole panel is draggable
|
|
54
|
+
const dragAreaElements = [panel].filter(Boolean);
|
|
55
|
+
// ✅ NEW: scroll + boundary detection
|
|
56
|
+
const body = panel.querySelector(".spb-body");
|
|
57
|
+
let isScrollActive = false;
|
|
58
|
+
const isAtBoundary = () => {
|
|
59
|
+
if (!body)
|
|
60
|
+
return true;
|
|
61
|
+
const atTop = body.scrollTop <= 0;
|
|
62
|
+
const atBottom = body.scrollTop + body.clientHeight >= body.scrollHeight - 1;
|
|
63
|
+
return atTop || atBottom;
|
|
60
64
|
};
|
|
65
|
+
body?.addEventListener("scroll", () => {
|
|
66
|
+
isScrollActive = true;
|
|
67
|
+
clearTimeout(body._scrollTimer);
|
|
68
|
+
body._scrollTimer = setTimeout(() => {
|
|
69
|
+
isScrollActive = false;
|
|
70
|
+
}, 80);
|
|
71
|
+
});
|
|
72
|
+
const getDiff = (current) => isVertical ? current - startPos : current - startPos;
|
|
61
73
|
const getTransform = (diff) => {
|
|
62
74
|
switch (position) {
|
|
63
75
|
case "bottom":
|
|
@@ -73,6 +85,9 @@ export const SlidingPanel = ({ isOpen, onClose, children, height = "80%", width
|
|
|
73
85
|
}
|
|
74
86
|
};
|
|
75
87
|
const startDrag = (pos) => {
|
|
88
|
+
// ✅ CHANGE 2: block drag if scrolling or not at boundary
|
|
89
|
+
if (isScrollActive || !isAtBoundary())
|
|
90
|
+
return;
|
|
76
91
|
startPos = pos;
|
|
77
92
|
isDragging = true;
|
|
78
93
|
panel.style.transition = "none";
|
|
@@ -80,6 +95,8 @@ export const SlidingPanel = ({ isOpen, onClose, children, height = "80%", width
|
|
|
80
95
|
const moveDrag = (pos) => {
|
|
81
96
|
if (!isDragging)
|
|
82
97
|
return;
|
|
98
|
+
if (isScrollActive)
|
|
99
|
+
return;
|
|
83
100
|
currentPos = pos;
|
|
84
101
|
const diff = getDiff(currentPos);
|
|
85
102
|
panel.style.transform = getTransform(diff);
|
|
@@ -90,57 +107,51 @@ export const SlidingPanel = ({ isOpen, onClose, children, height = "80%", width
|
|
|
90
107
|
isDragging = false;
|
|
91
108
|
const diff = getDiff(currentPos);
|
|
92
109
|
panel.style.transition = "transform 0.3s ease";
|
|
93
|
-
const threshold = 100;
|
|
110
|
+
const threshold = 100;
|
|
94
111
|
let shouldClose = false;
|
|
95
112
|
switch (position) {
|
|
96
113
|
case "bottom":
|
|
97
114
|
shouldClose = diff > threshold;
|
|
98
|
-
panel.style.transform = shouldClose
|
|
99
|
-
? `translate(-50%, 100%)`
|
|
100
|
-
: `translate(-50%, 0)`;
|
|
101
115
|
break;
|
|
102
116
|
case "top":
|
|
103
117
|
shouldClose = diff < -threshold;
|
|
104
|
-
panel.style.transform = shouldClose
|
|
105
|
-
? `translate(-50%, -100%)`
|
|
106
|
-
: `translate(-50%, 0)`;
|
|
107
118
|
break;
|
|
108
119
|
case "left":
|
|
109
120
|
shouldClose = diff < -threshold;
|
|
110
|
-
panel.style.transform = shouldClose
|
|
111
|
-
? `translate(-100%, -50%)`
|
|
112
|
-
: `translate(0, -50%)`;
|
|
113
121
|
break;
|
|
114
122
|
case "right":
|
|
115
123
|
shouldClose = diff > threshold;
|
|
116
|
-
panel.style.transform = shouldClose
|
|
117
|
-
? `translate(100%, -50%)`
|
|
118
|
-
: `translate(0, -50%)`;
|
|
119
124
|
break;
|
|
120
125
|
}
|
|
121
126
|
if (shouldClose) {
|
|
122
127
|
switch (position) {
|
|
123
|
-
case "
|
|
124
|
-
panel.style.transform =
|
|
125
|
-
break;
|
|
126
|
-
case "left":
|
|
127
|
-
panel.style.transform = `translate(-100%, -50%)`;
|
|
128
|
+
case "bottom":
|
|
129
|
+
panel.style.transform = "translate(-50%, 100%)";
|
|
128
130
|
break;
|
|
129
131
|
case "top":
|
|
130
|
-
panel.style.transform =
|
|
132
|
+
panel.style.transform = "translate(-50%, -100%)";
|
|
131
133
|
break;
|
|
132
|
-
case "
|
|
133
|
-
panel.style.transform =
|
|
134
|
+
case "left":
|
|
135
|
+
panel.style.transform = "translate(-100%, -50%)";
|
|
136
|
+
break;
|
|
137
|
+
case "right":
|
|
138
|
+
panel.style.transform = "translate(100%, -50%)";
|
|
134
139
|
break;
|
|
135
140
|
}
|
|
136
|
-
setTimeout(
|
|
141
|
+
setTimeout(onClose, 250);
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
panel.style.transform =
|
|
145
|
+
position === "bottom" || position === "top"
|
|
146
|
+
? "translate(-50%, 0)"
|
|
147
|
+
: "translate(0, -50%)";
|
|
137
148
|
}
|
|
138
149
|
};
|
|
139
|
-
//
|
|
150
|
+
// touch
|
|
140
151
|
const onTouchStart = (e) => startDrag(isVertical ? e.touches[0].clientY : e.touches[0].clientX);
|
|
141
152
|
const onTouchMove = (e) => moveDrag(isVertical ? e.touches[0].clientY : e.touches[0].clientX);
|
|
142
153
|
const onTouchEnd = () => endDrag();
|
|
143
|
-
//
|
|
154
|
+
// mouse
|
|
144
155
|
const onMouseDown = (e) => startDrag(isVertical ? e.clientY : e.clientX);
|
|
145
156
|
const onMouseMove = (e) => moveDrag(isVertical ? e.clientY : e.clientX);
|
|
146
157
|
const onMouseUp = () => endDrag();
|
|
@@ -165,6 +176,6 @@ export const SlidingPanel = ({ isOpen, onClose, children, height = "80%", width
|
|
|
165
176
|
}, [onClose, position]);
|
|
166
177
|
if (!visible && !isOpen)
|
|
167
178
|
return null;
|
|
168
|
-
return (_jsxs("div", { className: `spb-root ${animateOpen ? "spb-open" : "spb-close"}`,
|
|
179
|
+
return (_jsxs("div", { className: `spb-root ${animateOpen ? "spb-open" : "spb-close"}`, children: [_jsx("div", { className: "spb-backdrop", onClick: () => closeOnOverlayClick && onClose() }), _jsxs("div", { ref: panelRef, className: `spb-panel spb-panel--position-${position}`, style: { height, width }, role: "dialog", "aria-modal": "true", onClick: (e) => e.stopPropagation(), children: [_jsx("div", { ref: headerRef, className: "spb-header", children: _jsx("button", { className: "spb-close-btn", onClick: onClose, children: "\u00D7" }) }), _jsx("div", { className: "spb-body", children: children })] })] }));
|
|
169
180
|
};
|
|
170
181
|
export default SlidingPanel;
|