@skalfa/skalfa-component 1.0.49 → 1.0.51

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -26,6 +26,21 @@ function sizeToPx(value) {
26
26
  }
27
27
  return parseFloat(v) || 0;
28
28
  }
29
+ function findScrollableParent(target, stopAt) {
30
+ if (typeof window === "undefined")
31
+ return null;
32
+ let el = target;
33
+ while (el && el !== stopAt && el !== document.body) {
34
+ const style = window.getComputedStyle(el);
35
+ const overflowY = style.overflowY;
36
+ const canScroll = (overflowY === "auto" || overflowY === "scroll") && el.scrollHeight > el.clientHeight;
37
+ if (canScroll) {
38
+ return el;
39
+ }
40
+ el = el.parentElement;
41
+ }
42
+ return null;
43
+ }
29
44
  const BottomSheet = ({ show, children, onClose, size = 500, maxSize, footer, className = "", }) => {
30
45
  const scrollRef = (0, react_1.useRef)(null);
31
46
  const sheetRef = (0, react_1.useRef)(null);
@@ -34,104 +49,111 @@ const BottomSheet = ({ show, children, onClose, size = 500, maxSize, footer, cla
34
49
  const dragging = (0, react_1.useRef)(false);
35
50
  const [offset, setOffset] = (0, react_1.useState)(0);
36
51
  const [isExpanded, setIsExpanded] = (0, react_1.useState)(false);
37
- const [scrollLocked, setScrollLocked] = (0, react_1.useState)(false);
38
- const [contentScrollable, setContentScrollable] = (0, react_1.useState)(false);
39
- const realMaxSize = maxSize ?? size;
52
+ const [isDragging, setIsDragging] = (0, react_1.useState)(false);
53
+ const isClosing = (0, react_1.useRef)(false);
54
+ const canExpand = Boolean(maxSize) && sizeToPx(maxSize) > sizeToPx(size);
55
+ const realMaxSize = canExpand && (typeof maxSize === "number" || typeof maxSize === "string") ? maxSize : size;
56
+ const canDragUp = canExpand && !isExpanded;
40
57
  const clamp = (v) => {
41
58
  const max = window.innerHeight;
42
- return Math.max(-200, Math.min(v, max));
43
- };
44
- const animateTo = (target, onFinish) => {
45
- lastY.current = target;
46
- setOffset(clamp(target));
47
- onFinish?.();
59
+ const min = canDragUp ? -200 : 0;
60
+ return Math.max(min, Math.min(v, max));
48
61
  };
49
62
  (0, react_1.useEffect)(() => {
50
63
  if (show) {
51
64
  lastY.current = 0;
52
65
  setOffset(0);
53
66
  setIsExpanded(false);
67
+ isClosing.current = false;
54
68
  }
55
69
  else {
56
70
  const t = setTimeout(() => {
57
71
  lastY.current = 0;
58
72
  setOffset(0);
59
73
  setIsExpanded(false);
60
- }, 250);
74
+ }, 300);
61
75
  return () => clearTimeout(t);
62
76
  }
63
77
  }, [show]);
64
- const onStart = (clientY) => {
65
- const sc = scrollRef.current;
66
- if (sc && sc.scrollTop < 0) {
67
- setScrollLocked(true);
78
+ const onStart = (clientY, target) => {
79
+ const targetEl = target;
80
+ // Abaikan drag jika menekan elemen interaktif seperti button, input, link
81
+ if (targetEl?.closest("button, input, textarea, select, a, [role='button']")) {
82
+ dragging.current = false;
83
+ return;
84
+ }
85
+ // Abaikan drag jika sentuhan berada di dalam elemen yang sedang bisa di-scroll
86
+ const scrollableParent = findScrollableParent(targetEl, sheetRef.current);
87
+ if (scrollableParent) {
88
+ dragging.current = false;
68
89
  return;
69
90
  }
70
- setScrollLocked(false);
71
91
  dragging.current = true;
72
92
  startY.current = clientY;
73
93
  lastY.current = offset;
94
+ setIsDragging(true);
74
95
  };
75
96
  const onMove = (clientY) => {
76
- if (scrollLocked)
77
- return;
78
97
  if (!dragging.current)
79
98
  return;
80
99
  const diff = clientY - startY.current;
81
100
  setOffset(clamp(lastY.current + diff));
82
101
  };
83
102
  const onEnd = () => {
84
- if (scrollLocked) {
85
- setScrollLocked(false);
86
- return;
87
- }
88
103
  if (!dragging.current)
89
104
  return;
90
105
  dragging.current = false;
106
+ setIsDragging(false);
91
107
  const current = offset;
92
108
  const thresholdDown = 120;
93
109
  const thresholdUp = -40;
94
- if (!isExpanded && current < thresholdUp && maxSize !== undefined) {
110
+ // 1. Expand ke maxSize jika di-drag ke atas melebihi threshold
111
+ if (!isExpanded && current < thresholdUp && canExpand) {
95
112
  setIsExpanded(true);
96
- animateTo(0);
113
+ setOffset(0);
114
+ lastY.current = 0;
97
115
  return;
98
116
  }
117
+ // 2. Collapse kembali ke size normal jika sedang expanded dan di-drag ke bawah
99
118
  if (isExpanded && current > thresholdDown) {
100
119
  setIsExpanded(false);
101
- animateTo(0);
120
+ setOffset(0);
121
+ lastY.current = 0;
102
122
  return;
103
123
  }
124
+ // 3. Menutup bottom sheet jika di-drag ke bawah melebihi threshold
104
125
  if (!isExpanded && current > thresholdDown) {
105
- animateTo(window.innerHeight, () => {
126
+ if (isClosing.current)
127
+ return;
128
+ isClosing.current = true;
129
+ setOffset(window.innerHeight);
130
+ setTimeout(() => {
106
131
  onClose();
107
132
  lastY.current = 0;
108
133
  setOffset(0);
109
- });
134
+ isClosing.current = false;
135
+ }, 300);
110
136
  return;
111
137
  }
112
- animateTo(0);
138
+ // 4. Kembali ke posisi 0 (bouncing back smoothly)
139
+ setOffset(0);
140
+ lastY.current = 0;
113
141
  };
114
142
  const collapsedPx = sizeToPx(size);
115
143
  const expandedPx = sizeToPx(realMaxSize);
116
144
  const topPx = isExpanded ? window.innerHeight - expandedPx : window.innerHeight - collapsedPx;
117
145
  const bindTouch = {
118
- onTouchStart: (e) => onStart(e.touches[0].clientY),
146
+ onTouchStart: (e) => onStart(e.touches[0].clientY, e.target),
119
147
  onTouchMove: (e) => onMove(e.touches[0].clientY),
120
148
  onTouchEnd: () => onEnd(),
149
+ onTouchCancel: () => onEnd(),
121
150
  };
122
151
  const bindMouse = {
123
- onMouseDown: (e) => onStart(e.clientY),
152
+ onMouseDown: (e) => onStart(e.clientY, e.target),
124
153
  onMouseMove: (e) => dragging.current && onMove(e.clientY),
125
154
  onMouseUp: () => onEnd(),
126
155
  onMouseLeave: () => onEnd(),
127
156
  };
128
- (0, react_1.useEffect)(() => {
129
- const sc = scrollRef.current;
130
- if (!sc)
131
- return;
132
- const canScroll = sc.scrollHeight > sc.clientHeight;
133
- setContentScrollable(canScroll);
134
- }, [show, size, maxSize]);
135
157
  (0, react_1.useEffect)(() => {
136
158
  if (show) {
137
159
  history.pushState({ bottomsheet: true }, "");
@@ -148,13 +170,19 @@ const BottomSheet = ({ show, children, onClose, size = 500, maxSize, footer, cla
148
170
  window.addEventListener("popstate", onPopState);
149
171
  return () => window.removeEventListener("popstate", onPopState);
150
172
  }, [show, onClose]);
151
- return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("div", { className: (0, _utils_1.cn)("modal-backdrop", !show && "translate-y-full", (0, _utils_1.pcn)(className, "backdrop")), onClick: onClose }), (0, jsx_runtime_1.jsx)("div", { ref: sheetRef, className: "bottom-sheet", style: {
173
+ return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("div", { className: (0, _utils_1.cn)("modal-backdrop transition-opacity duration-300", !show && "opacity-0 pointer-events-none translate-y-full", (0, _utils_1.pcn)(className, "backdrop")), onClick: onClose }), (0, jsx_runtime_1.jsx)("div", { ref: sheetRef, className: "bottom-sheet", style: {
152
174
  top: show ? `${topPx}px` : "150vh",
153
175
  transform: `translateY(${offset}px)`,
154
- touchAction: "none",
155
- }, ...bindTouch, ...bindMouse, children: (0, jsx_runtime_1.jsxs)("div", { className: "bottom-sheet-container", children: [(0, jsx_runtime_1.jsx)("div", { className: "bottom-sheet-handle-wrapper", children: (0, jsx_runtime_1.jsx)("div", { className: "bottom-sheet-handle" }) }), (0, jsx_runtime_1.jsx)("div", { ref: scrollRef, className: "overflow-y-auto", style: {
176
+ transition: isDragging
177
+ ? "none"
178
+ : "top 300ms cubic-bezier(0.16, 1, 0.3, 1), transform 300ms cubic-bezier(0.16, 1, 0.3, 1)",
179
+ touchAction: "pan-y",
180
+ }, ...bindTouch, ...bindMouse, children: (0, jsx_runtime_1.jsxs)("div", { className: "bottom-sheet-container", children: [(0, jsx_runtime_1.jsx)("div", { className: "bottom-sheet-handle-wrapper", style: { touchAction: "none" }, children: (0, jsx_runtime_1.jsx)("div", { className: "bottom-sheet-handle" }) }), (0, jsx_runtime_1.jsx)("div", { ref: scrollRef, className: "overflow-y-auto", style: {
156
181
  height: isExpanded ? realMaxSize : size,
157
- touchAction: contentScrollable ? "auto" : "none",
182
+ transition: isDragging
183
+ ? "none"
184
+ : "height 300ms cubic-bezier(0.16, 1, 0.3, 1)",
185
+ touchAction: "pan-y",
158
186
  overscrollBehaviorY: "contain",
159
187
  }, children: children })] }) }), show && footer && ((0, jsx_runtime_1.jsx)("div", { className: "bottom-sheet-footer", children: footer }))] }));
160
188
  };
@@ -5,10 +5,11 @@ export interface FloatingPageProps {
5
5
  title?: string | ReactNode;
6
6
  children?: any;
7
7
  tip?: string | ReactNode;
8
+ header?: false | ReactNode;
8
9
  footer?: string | ReactNode;
9
10
  size?: string | number;
10
11
  maxSize?: string | number;
11
12
  /** Use custom class with: "backdrop::", "header::", "footer::". */
12
13
  className?: string;
13
14
  }
14
- export declare function FloatingPageComponent({ show, onClose, title, children, tip, footer, className, size, maxSize, }: FloatingPageProps): import("@types/react").JSX.Element;
15
+ export declare function FloatingPageComponent({ show, onClose, title, children, tip, footer, header, className, size, maxSize, }: FloatingPageProps): import("@types/react").JSX.Element;
@@ -8,7 +8,7 @@ const Button_component_1 = require("../button/Button.component");
8
8
  const _utils_1 = require("@utils");
9
9
  const BottomSheet_component_1 = require("./BottomSheet.component");
10
10
  ;
11
- function FloatingPageComponent({ show, onClose, title, children, tip, footer, className = "", size, maxSize, }) {
11
+ function FloatingPageComponent({ show, onClose, title, children, tip, footer, header, className = "", size, maxSize, }) {
12
12
  const { isSm } = (0, _utils_1.useResponsive)();
13
13
  (0, react_1.useEffect)(() => {
14
14
  if (show) {
@@ -27,5 +27,5 @@ function FloatingPageComponent({ show, onClose, title, children, tip, footer, cl
27
27
  if (isSm) {
28
28
  return ((0, jsx_runtime_1.jsxs)(BottomSheet_component_1.BottomSheetComponent, { show: show, onClose: onClose, size: size, maxSize: maxSize, footer: footer, className: className, children: [(0, jsx_runtime_1.jsx)("div", { className: (0, _utils_1.cn)("modal-header", (0, _utils_1.pcn)(className, "header")), children: title && ((0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("h6", { className: "modal-title", children: title }), tip && (0, jsx_runtime_1.jsx)("p", { className: "modal-tip", children: tip })] })) }), show && children] }));
29
29
  }
30
- return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("div", { className: (0, _utils_1.cn)("modal-backdrop", !show && "opacity-0 scale-0 -translate-y-full", (0, _utils_1.pcn)(className, "backdrop")), onClick: () => onClose() }), (0, jsx_runtime_1.jsxs)("div", { className: (0, _utils_1.cn)("floating-page", !show && "top-[200vh] md:top-0 md:-right-[200vw]", (0, _utils_1.pcn)(className, "base")), children: [(0, jsx_runtime_1.jsxs)("div", { className: (0, _utils_1.cn)("modal-header", (0, _utils_1.pcn)(className, "header")), children: [title && ((0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("h6", { className: "modal-title", children: title }), tip && (0, jsx_runtime_1.jsx)("p", { className: "modal-tip", children: tip })] })), (0, jsx_runtime_1.jsx)(Button_component_1.ButtonComponent, { icon: "solid/times", variant: "simple", paint: "danger", onClick: () => onClose() })] }), show && children, footer && ((0, jsx_runtime_1.jsx)("div", { className: (0, _utils_1.cn)("modal-footer absolute bottom-0 w-full", (0, _utils_1.pcn)(className, "footer")), children: show && footer }))] })] }));
30
+ return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("div", { className: (0, _utils_1.cn)("modal-backdrop", !show && "opacity-0 scale-0 -translate-y-full", (0, _utils_1.pcn)(className, "backdrop")), onClick: () => onClose() }), (0, jsx_runtime_1.jsxs)("div", { className: (0, _utils_1.cn)("floating-page", !show && "top-[200vh] md:top-0 md:-right-[200vw]", (0, _utils_1.pcn)(className, "base")), children: [(header == undefined || header != false) && ((0, jsx_runtime_1.jsxs)("div", { className: (0, _utils_1.cn)("modal-header", (0, _utils_1.pcn)(className, "header")), children: [title && ((0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("h6", { className: "modal-title", children: title }), tip && (0, jsx_runtime_1.jsx)("p", { className: "modal-tip", children: tip })] })), (0, jsx_runtime_1.jsx)(Button_component_1.ButtonComponent, { icon: "solid/times", variant: "simple", paint: "danger", onClick: () => onClose() })] })), !!header && header, show && children, footer && ((0, jsx_runtime_1.jsx)("div", { className: (0, _utils_1.cn)("modal-footer absolute bottom-0 w-full", (0, _utils_1.pcn)(className, "footer")), children: show && footer }))] })] }));
31
31
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skalfa/skalfa-component",
3
- "version": "1.0.49",
3
+ "version": "1.0.51",
4
4
  "description": "Reusable UI components for Skalfa App.",
5
5
  "license": "MIT",
6
6
  "keywords": [
@@ -36,6 +36,21 @@ function sizeToPx(value: string | number | undefined): number {
36
36
  return parseFloat(v) || 0;
37
37
  }
38
38
 
39
+ function findScrollableParent(target: HTMLElement | null, stopAt: HTMLElement | null): HTMLElement | null {
40
+ if (typeof window === "undefined") return null;
41
+ let el = target;
42
+ while (el && el !== stopAt && el !== document.body) {
43
+ const style = window.getComputedStyle(el);
44
+ const overflowY = style.overflowY;
45
+ const canScroll = (overflowY === "auto" || overflowY === "scroll") && el.scrollHeight > el.clientHeight;
46
+ if (canScroll) {
47
+ return el;
48
+ }
49
+ el = el.parentElement;
50
+ }
51
+ return null;
52
+ }
53
+
39
54
  const BottomSheet = ({
40
55
  show,
41
56
  children,
@@ -52,22 +67,19 @@ const BottomSheet = ({
52
67
  const lastY = useRef(0);
53
68
  const dragging = useRef(false);
54
69
 
55
- const [offset, setOffset] = useState(0);
56
- const [isExpanded, setIsExpanded] = useState(false);
57
- const [scrollLocked, setScrollLocked] = useState(false);
58
- const [contentScrollable, setContentScrollable] = useState(false);
70
+ const [offset, setOffset] = useState(0);
71
+ const [isExpanded, setIsExpanded] = useState(false);
72
+ const [isDragging, setIsDragging] = useState(false);
73
+ const isClosing = useRef(false);
59
74
 
60
- const realMaxSize = maxSize ?? size;
75
+ const canExpand = Boolean(maxSize) && sizeToPx(maxSize) > sizeToPx(size);
76
+ const realMaxSize = canExpand && (typeof maxSize === "number" || typeof maxSize === "string") ? maxSize : size;
77
+ const canDragUp = canExpand && !isExpanded;
61
78
 
62
79
  const clamp = (v: number) => {
63
80
  const max = window.innerHeight;
64
- return Math.max(-200, Math.min(v, max));
65
- };
66
-
67
- const animateTo = (target: number, onFinish?: () => void) => {
68
- lastY.current = target;
69
- setOffset(clamp(target));
70
- onFinish?.();
81
+ const min = canDragUp ? -200 : 0;
82
+ return Math.max(min, Math.min(v, max));
71
83
  };
72
84
 
73
85
  useEffect(() => {
@@ -75,33 +87,40 @@ const BottomSheet = ({
75
87
  lastY.current = 0;
76
88
  setOffset(0);
77
89
  setIsExpanded(false);
90
+ isClosing.current = false;
78
91
  } else {
79
92
  const t = setTimeout(() => {
80
93
  lastY.current = 0;
81
94
  setOffset(0);
82
95
  setIsExpanded(false);
83
- }, 250);
96
+ }, 300);
84
97
  return () => clearTimeout(t);
85
98
  }
86
99
  }, [show]);
87
100
 
88
- const onStart = (clientY: number) => {
89
- const sc = scrollRef.current;
101
+ const onStart = (clientY: number, target: EventTarget | null) => {
102
+ const targetEl = target as HTMLElement | null;
90
103
 
91
- if (sc && sc.scrollTop < 0) {
92
- setScrollLocked(true);
104
+ // Abaikan drag jika menekan elemen interaktif seperti button, input, link
105
+ if (targetEl?.closest("button, input, textarea, select, a, [role='button']")) {
106
+ dragging.current = false;
93
107
  return;
94
108
  }
95
109
 
96
- setScrollLocked(false);
110
+ // Abaikan drag jika sentuhan berada di dalam elemen yang sedang bisa di-scroll
111
+ const scrollableParent = findScrollableParent(targetEl, sheetRef.current);
112
+ if (scrollableParent) {
113
+ dragging.current = false;
114
+ return;
115
+ }
97
116
 
98
117
  dragging.current = true;
99
118
  startY.current = clientY;
100
119
  lastY.current = offset;
120
+ setIsDragging(true);
101
121
  };
102
122
 
103
123
  const onMove = (clientY: number) => {
104
- if (scrollLocked) return;
105
124
  if (!dragging.current) return;
106
125
 
107
126
  const diff = clientY - startY.current;
@@ -109,41 +128,48 @@ const BottomSheet = ({
109
128
  };
110
129
 
111
130
  const onEnd = () => {
112
- if (scrollLocked) {
113
- setScrollLocked(false);
114
- return;
115
- }
116
-
117
131
  if (!dragging.current) return;
118
132
  dragging.current = false;
133
+ setIsDragging(false);
119
134
 
120
135
  const current = offset;
121
136
 
122
137
  const thresholdDown = 120;
123
138
  const thresholdUp = -40;
124
139
 
125
- if (!isExpanded && current < thresholdUp && maxSize !== undefined) {
140
+ // 1. Expand ke maxSize jika di-drag ke atas melebihi threshold
141
+ if (!isExpanded && current < thresholdUp && canExpand) {
126
142
  setIsExpanded(true);
127
- animateTo(0);
143
+ setOffset(0);
144
+ lastY.current = 0;
128
145
  return;
129
146
  }
130
147
 
148
+ // 2. Collapse kembali ke size normal jika sedang expanded dan di-drag ke bawah
131
149
  if (isExpanded && current > thresholdDown) {
132
150
  setIsExpanded(false);
133
- animateTo(0);
151
+ setOffset(0);
152
+ lastY.current = 0;
134
153
  return;
135
154
  }
136
155
 
156
+ // 3. Menutup bottom sheet jika di-drag ke bawah melebihi threshold
137
157
  if (!isExpanded && current > thresholdDown) {
138
- animateTo(window.innerHeight, () => {
158
+ if (isClosing.current) return;
159
+ isClosing.current = true;
160
+ setOffset(window.innerHeight);
161
+ setTimeout(() => {
139
162
  onClose();
140
163
  lastY.current = 0;
141
164
  setOffset(0);
142
- });
165
+ isClosing.current = false;
166
+ }, 300);
143
167
  return;
144
168
  }
145
169
 
146
- animateTo(0);
170
+ // 4. Kembali ke posisi 0 (bouncing back smoothly)
171
+ setOffset(0);
172
+ lastY.current = 0;
147
173
  };
148
174
 
149
175
  const collapsedPx = sizeToPx(size);
@@ -152,26 +178,19 @@ const BottomSheet = ({
152
178
  const topPx = isExpanded ? window.innerHeight - expandedPx : window.innerHeight - collapsedPx;
153
179
 
154
180
  const bindTouch = {
155
- onTouchStart : (e: TouchEvent) => onStart(e.touches[0].clientY),
181
+ onTouchStart : (e: TouchEvent) => onStart(e.touches[0].clientY, e.target),
156
182
  onTouchMove : (e: TouchEvent) => onMove(e.touches[0].clientY),
157
- onTouchEnd : () => onEnd(),
183
+ onTouchEnd : () => onEnd(),
184
+ onTouchCancel: () => onEnd(),
158
185
  };
159
186
 
160
187
  const bindMouse = {
161
- onMouseDown : (e: MouseEvent) => onStart(e.clientY),
162
- onMouseMove : (e: MouseEvent) => dragging.current && onMove(e.clientY),
163
- onMouseUp : () => onEnd(),
164
- onMouseLeave : () => onEnd(),
188
+ onMouseDown : (e: MouseEvent) => onStart(e.clientY, e.target),
189
+ onMouseMove : (e: MouseEvent) => dragging.current && onMove(e.clientY),
190
+ onMouseUp : () => onEnd(),
191
+ onMouseLeave : () => onEnd(),
165
192
  };
166
193
 
167
- useEffect(() => {
168
- const sc = scrollRef.current;
169
- if (!sc) return;
170
-
171
- const canScroll = sc.scrollHeight > sc.clientHeight;
172
- setContentScrollable(canScroll);
173
- }, [show, size, maxSize]);
174
-
175
194
  useEffect(() => {
176
195
  if (show) {
177
196
  history.pushState({ bottomsheet: true }, "");
@@ -196,8 +215,8 @@ const BottomSheet = ({
196
215
  <>
197
216
  <div
198
217
  className={cn(
199
- "modal-backdrop",
200
- !show && "translate-y-full",
218
+ "modal-backdrop transition-opacity duration-300",
219
+ !show && "opacity-0 pointer-events-none translate-y-full",
201
220
  pcn<CT>(className, "backdrop"),
202
221
  )}
203
222
  onClick={onClose}
@@ -209,13 +228,19 @@ const BottomSheet = ({
209
228
  style={{
210
229
  top: show ? `${topPx}px` : "150vh",
211
230
  transform: `translateY(${offset}px)`,
212
- touchAction: "none",
231
+ transition: isDragging
232
+ ? "none"
233
+ : "top 300ms cubic-bezier(0.16, 1, 0.3, 1), transform 300ms cubic-bezier(0.16, 1, 0.3, 1)",
234
+ touchAction: "pan-y",
213
235
  }}
214
236
  {...bindTouch}
215
237
  {...bindMouse}
216
238
  >
217
239
  <div className="bottom-sheet-container">
218
- <div className="bottom-sheet-handle-wrapper">
240
+ <div
241
+ className="bottom-sheet-handle-wrapper"
242
+ style={{ touchAction: "none" }}
243
+ >
219
244
  <div className="bottom-sheet-handle" />
220
245
  </div>
221
246
 
@@ -224,7 +249,10 @@ const BottomSheet = ({
224
249
  className="overflow-y-auto"
225
250
  style={{
226
251
  height: isExpanded ? realMaxSize : size,
227
- touchAction: contentScrollable ? "auto" : "none",
252
+ transition: isDragging
253
+ ? "none"
254
+ : "height 300ms cubic-bezier(0.16, 1, 0.3, 1)",
255
+ touchAction: "pan-y",
228
256
  overscrollBehaviorY: "contain",
229
257
  }}
230
258
  >
@@ -15,6 +15,7 @@ export interface FloatingPageProps {
15
15
  title ?: string | ReactNode;
16
16
  children ?: any;
17
17
  tip ?: string | ReactNode;
18
+ header ?: false | ReactNode;
18
19
  footer ?: string | ReactNode;
19
20
  size ?: string | number;
20
21
  maxSize ?: string | number;
@@ -33,6 +34,7 @@ export function FloatingPageComponent({
33
34
  children,
34
35
  tip,
35
36
  footer,
37
+ header,
36
38
  className = "",
37
39
  size,
38
40
  maxSize,
@@ -98,22 +100,25 @@ export function FloatingPageComponent({
98
100
  pcn<CT>(className, "base"),
99
101
  )}
100
102
  >
101
- <div className={cn("modal-header", pcn<CT>(className, "header"))}>
102
- {title && (
103
- <div>
104
- <h6 className="modal-title">{title}</h6>
105
- {tip && <p className="modal-tip">{tip}</p>}
106
- </div>
107
- )}
103
+ {(header == undefined || header != false) && (
104
+ <div className={cn("modal-header", pcn<CT>(className, "header"))}>
105
+ {title && (
106
+ <div>
107
+ <h6 className="modal-title">{title}</h6>
108
+ {tip && <p className="modal-tip">{tip}</p>}
109
+ </div>
110
+ )}
108
111
 
109
- <ButtonComponent
110
- icon="solid/times"
111
- variant="simple"
112
- paint="danger"
113
- onClick={() => onClose()}
114
- />
115
- </div>
116
-
112
+ <ButtonComponent
113
+ icon="solid/times"
114
+ variant="simple"
115
+ paint="danger"
116
+ onClick={() => onClose()}
117
+ />
118
+ </div>
119
+ )}
120
+
121
+ {!!header && header}
117
122
 
118
123
  {show && children}
119
124