@trackunit/react-drawer 0.1.31 → 0.2.0
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/index.cjs.js
CHANGED
|
@@ -98,7 +98,7 @@ const cvaOverlayContainer = cssClassVarianceUtilities.cvaMerge([
|
|
|
98
98
|
variants: {
|
|
99
99
|
open: {
|
|
100
100
|
true: "z-popover opacity-100",
|
|
101
|
-
false: "z-
|
|
101
|
+
false: "z-hidden pointer-events-none opacity-0",
|
|
102
102
|
},
|
|
103
103
|
},
|
|
104
104
|
});
|
|
@@ -336,15 +336,19 @@ const getElementYTranslation = (element) => {
|
|
|
336
336
|
const matrix = new DOMMatrix(transform);
|
|
337
337
|
return matrix.m42;
|
|
338
338
|
};
|
|
339
|
+
const isPassedThreshold = (dragDistance, threshold) => {
|
|
340
|
+
return dragDistance >= threshold;
|
|
341
|
+
};
|
|
339
342
|
/**
|
|
340
343
|
* Hook for handling swipe events on the drawer.
|
|
341
344
|
*/
|
|
342
|
-
const useSwipeHandlers = ({ ref, closingThreshold, onCloseGesture, onOpenGesture }) => {
|
|
345
|
+
const useSwipeHandlers = ({ ref, closingThreshold, onCloseGesture, onOpenGesture, onDragEnd, }) => {
|
|
343
346
|
const [isDragging, setIsDragging] = react.useState(false);
|
|
344
347
|
const [dragDistance, setDragDistance] = react.useState(0);
|
|
345
348
|
const currentY = react.useRef(0);
|
|
346
349
|
const currentHeight = react.useRef(0);
|
|
347
350
|
const geometry = reactComponents.useGeometry(ref);
|
|
351
|
+
const threshold = currentHeight.current * closingThreshold;
|
|
348
352
|
const handlers = reactSwipeable.useSwipeable({
|
|
349
353
|
onTouchStartOrOnMouseDown: () => {
|
|
350
354
|
const y = getElementYTranslation(ref.current);
|
|
@@ -361,22 +365,22 @@ const useSwipeHandlers = ({ ref, closingThreshold, onCloseGesture, onOpenGesture
|
|
|
361
365
|
}
|
|
362
366
|
},
|
|
363
367
|
onSwipedDown: (e) => {
|
|
364
|
-
|
|
365
|
-
if (e.absY > threshold && onCloseGesture) {
|
|
368
|
+
if (isPassedThreshold(e.absY, threshold) && onCloseGesture) {
|
|
366
369
|
onCloseGesture();
|
|
367
370
|
}
|
|
368
371
|
setIsDragging(false);
|
|
369
372
|
setDragDistance(0);
|
|
370
373
|
},
|
|
371
374
|
onSwipedUp: (e) => {
|
|
372
|
-
|
|
373
|
-
if (e.absY > threshold && onOpenGesture) {
|
|
375
|
+
if (isPassedThreshold(e.absY, threshold) && onOpenGesture) {
|
|
374
376
|
onOpenGesture();
|
|
375
377
|
}
|
|
376
378
|
setIsDragging(false);
|
|
377
379
|
setDragDistance(0);
|
|
378
380
|
},
|
|
379
|
-
onSwiped: () =>
|
|
381
|
+
onSwiped: (e) => {
|
|
382
|
+
onDragEnd && onDragEnd(e.deltaY, e.velocity);
|
|
383
|
+
},
|
|
380
384
|
trackMouse: true,
|
|
381
385
|
trackTouch: true,
|
|
382
386
|
preventScrollOnSwipe: true,
|
|
@@ -400,6 +404,8 @@ const cvaPuller = cssClassVarianceUtilities.cvaMerge(["pt-1", "pb-4", "flex", "i
|
|
|
400
404
|
const cvaPullerIcon = cssClassVarianceUtilities.cvaMerge(["block", "h-1", "w-8", "rounded-full", "bg-gray-400"]);
|
|
401
405
|
|
|
402
406
|
const CLOSING_THRESHOLD = 0.15;
|
|
407
|
+
const VELOCITY_THRESHOLD = 0.3;
|
|
408
|
+
const SNAP_POINT_TRANSITION = "transform 500ms cubic-bezier(0.32,0.72,0,1)";
|
|
403
409
|
/**
|
|
404
410
|
*
|
|
405
411
|
* SwipeableDrawer is a component that wraps the Drawer component to add swipeable functionality.
|
|
@@ -407,52 +413,133 @@ const CLOSING_THRESHOLD = 0.15;
|
|
|
407
413
|
* The component manages its docked state based on the open prop and keepMountedWhenClosed prop.
|
|
408
414
|
* It also applies styles dynamically based on whether the drawer is being dragged.
|
|
409
415
|
*/
|
|
410
|
-
const SwipeableDrawer = react.forwardRef(({ open, onClose, onOpenGesture, children, keepMountedWhenClosed, className, position = "bottom", ...others }, ref) => {
|
|
411
|
-
const
|
|
412
|
-
react.
|
|
413
|
-
const
|
|
416
|
+
const SwipeableDrawer = react.forwardRef(({ open, onClose, onOpenGesture, onSnapPointChange, children, keepMountedWhenClosed, className, position = "bottom", snapPoints, activeSnapPoint: activeSnapPointProp = null, container, ...others }, ref) => {
|
|
417
|
+
const drawerRef = react.useRef(null);
|
|
418
|
+
const containerRef = react.useRef(container !== null && container !== void 0 ? container : null);
|
|
419
|
+
const containerGeometry = reactComponents.useGeometry(containerRef);
|
|
420
|
+
react.useImperativeHandle(ref, () => drawerRef.current);
|
|
421
|
+
const [activeSnapPoint, setActiveSnapPoint] = react.useState(activeSnapPointProp);
|
|
422
|
+
const [snapPointOffsets, setSnapPointOffsets] = react.useState([]);
|
|
423
|
+
const activeSnapPointIndex = react.useMemo(() => { var _a; return (_a = snapPoints === null || snapPoints === void 0 ? void 0 : snapPoints.findIndex(snapPoint => snapPoint === activeSnapPoint)) !== null && _a !== void 0 ? _a : -1; }, [snapPoints, activeSnapPoint]);
|
|
424
|
+
const onHandleDragEnd = react.useCallback((draggedBy, velocity) => {
|
|
425
|
+
var _a, _b, _c;
|
|
426
|
+
if (!snapPoints || !snapPoints.length) {
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
const direction = draggedBy > 0 ? -1 : 1;
|
|
430
|
+
// If the velocity is greater than the threshold and the dragged distance is less than 40% of the container height, snap to the next snap point
|
|
431
|
+
if (velocity > VELOCITY_THRESHOLD && Math.abs(draggedBy) < containerGeometry.height * 0.4) {
|
|
432
|
+
const newSnapPoint = (_a = snapPoints[activeSnapPointIndex + direction]) !== null && _a !== void 0 ? _a : null;
|
|
433
|
+
if (newSnapPoint) {
|
|
434
|
+
setActiveSnapPoint(newSnapPoint);
|
|
435
|
+
onSnapPointChange === null || onSnapPointChange === void 0 ? void 0 : onSnapPointChange(newSnapPoint);
|
|
436
|
+
}
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
// Otherwise, find the closest snap point
|
|
440
|
+
// This is when the user is dragging but not swiping.
|
|
441
|
+
// For example, when the user is dragging the drawer to the bottom but not fast enough to trigger a swipe.
|
|
442
|
+
const currentPosition = ((_b = snapPointOffsets[activeSnapPointIndex]) !== null && _b !== void 0 ? _b : 0) + draggedBy;
|
|
443
|
+
const closestSnapPoint = snapPointOffsets.reduce((prev, curr) => {
|
|
444
|
+
if (typeof prev !== "number" || typeof curr !== "number") {
|
|
445
|
+
return prev;
|
|
446
|
+
}
|
|
447
|
+
return Math.abs(curr - currentPosition) < Math.abs(prev - currentPosition) ? curr : prev;
|
|
448
|
+
});
|
|
449
|
+
const snapPointIndex = snapPointOffsets.findIndex(offset => offset === closestSnapPoint);
|
|
450
|
+
const nextSnapPoint = (_c = snapPoints[snapPointIndex]) !== null && _c !== void 0 ? _c : null;
|
|
451
|
+
if (!nextSnapPoint) {
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
setActiveSnapPoint(nextSnapPoint);
|
|
455
|
+
onSnapPointChange === null || onSnapPointChange === void 0 ? void 0 : onSnapPointChange(nextSnapPoint);
|
|
456
|
+
}, [snapPoints, snapPointOffsets, activeSnapPointIndex, containerGeometry, onSnapPointChange]);
|
|
414
457
|
const { handlers, isDragging, dragDistance } = useSwipeHandlers({
|
|
415
|
-
ref:
|
|
458
|
+
ref: drawerRef,
|
|
416
459
|
closingThreshold: CLOSING_THRESHOLD,
|
|
417
|
-
onCloseGesture:
|
|
418
|
-
|
|
460
|
+
onCloseGesture: () => {
|
|
461
|
+
if (!snapPoints) {
|
|
462
|
+
onClose && onClose();
|
|
463
|
+
}
|
|
464
|
+
},
|
|
465
|
+
onOpenGesture: () => {
|
|
466
|
+
if (!snapPoints) {
|
|
467
|
+
onOpenGesture && onOpenGesture();
|
|
468
|
+
}
|
|
469
|
+
},
|
|
470
|
+
onDragEnd: onHandleDragEnd,
|
|
419
471
|
});
|
|
420
472
|
useScrollBlock({ isDisabled: !open || isDragging });
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
transition: "none",
|
|
473
|
+
react.useEffect(() => {
|
|
474
|
+
if (!snapPoints || !drawerRef.current) {
|
|
475
|
+
return;
|
|
425
476
|
}
|
|
426
|
-
|
|
477
|
+
const containerSize = containerRef.current ? containerGeometry.height : window.innerHeight;
|
|
478
|
+
const newSnapPointOffsets = snapPoints.map(snapPoint => {
|
|
479
|
+
// If snapPoint is a number, it is a percentage of the container size
|
|
480
|
+
if (typeof snapPoint === "number") {
|
|
481
|
+
const height = Math.floor(containerSize * snapPoint);
|
|
482
|
+
return containerSize - height;
|
|
483
|
+
}
|
|
484
|
+
// If snapPoint is a string, it is a pixel value
|
|
485
|
+
if (snapPoint.endsWith("px")) {
|
|
486
|
+
return containerSize - parseInt(snapPoint.slice(0, -1), 10);
|
|
487
|
+
}
|
|
488
|
+
return 0;
|
|
489
|
+
});
|
|
490
|
+
setSnapPointOffsets(newSnapPointOffsets);
|
|
491
|
+
}, [snapPoints, drawerRef, containerRef, containerGeometry]);
|
|
492
|
+
react.useEffect(() => {
|
|
493
|
+
if (container) {
|
|
494
|
+
containerRef.current = container;
|
|
495
|
+
}
|
|
496
|
+
}, [container]);
|
|
427
497
|
react.useEffect(() => {
|
|
428
|
-
|
|
498
|
+
var _a;
|
|
499
|
+
if (!snapPoints || snapPoints.length === 0) {
|
|
429
500
|
return;
|
|
430
501
|
}
|
|
431
|
-
|
|
432
|
-
|
|
502
|
+
const snapPointIndex = snapPoints.findIndex(offset => offset === activeSnapPointProp);
|
|
503
|
+
const nextSnapPoint = (_a = snapPoints[snapPointIndex > -1 ? snapPointIndex : 0]) !== null && _a !== void 0 ? _a : null;
|
|
504
|
+
if (!nextSnapPoint) {
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
setActiveSnapPoint(nextSnapPoint);
|
|
508
|
+
onSnapPointChange === null || onSnapPointChange === void 0 ? void 0 : onSnapPointChange(nextSnapPoint);
|
|
509
|
+
}, [snapPoints, activeSnapPointProp, onSnapPointChange]);
|
|
510
|
+
const drawerStyle = react.useMemo(() => {
|
|
511
|
+
if (isDragging) {
|
|
512
|
+
return {
|
|
513
|
+
transform: `translateY(${dragDistance}px)`,
|
|
514
|
+
transition: "none",
|
|
515
|
+
};
|
|
433
516
|
}
|
|
434
|
-
|
|
435
|
-
|
|
517
|
+
if (snapPoints && snapPointOffsets.length > 0 && activeSnapPointIndex !== -1) {
|
|
518
|
+
return {
|
|
519
|
+
transform: `translateY(${snapPointOffsets[activeSnapPointIndex]}px)`,
|
|
520
|
+
transition: SNAP_POINT_TRANSITION,
|
|
521
|
+
};
|
|
436
522
|
}
|
|
437
|
-
|
|
523
|
+
return {};
|
|
524
|
+
}, [isDragging, dragDistance, snapPointOffsets, activeSnapPointIndex, snapPoints]);
|
|
438
525
|
react.useEffect(() => {
|
|
439
526
|
var _a, _b;
|
|
440
|
-
if (
|
|
441
|
-
|
|
442
|
-
drawerRefs.current.style.transition = (_b = drawerStyle.transition) !== null && _b !== void 0 ? _b : "";
|
|
527
|
+
if (!drawerRef.current) {
|
|
528
|
+
return;
|
|
443
529
|
}
|
|
444
|
-
|
|
445
|
-
|
|
530
|
+
if (open) {
|
|
531
|
+
drawerRef.current.style.transform = (_a = drawerStyle.transform) !== null && _a !== void 0 ? _a : "";
|
|
532
|
+
drawerRef.current.style.transition = (_b = drawerStyle.transition) !== null && _b !== void 0 ? _b : "";
|
|
533
|
+
}
|
|
534
|
+
else {
|
|
535
|
+
drawerRef.current.style.transform = "";
|
|
536
|
+
drawerRef.current.style.transition = "";
|
|
537
|
+
}
|
|
538
|
+
}, [drawerStyle, drawerRef, open]);
|
|
539
|
+
return (jsxRuntime.jsxs(Drawer, { className: cvaSwipeableDrawer({ className }), keepMountedWhenClosed: keepMountedWhenClosed, onClose: onClose, open: open, position: position, ref: drawerRef, ...others, children: [jsxRuntime.jsx(DrawerPuller, { ...handlers }), children] }));
|
|
446
540
|
});
|
|
447
541
|
SwipeableDrawer.displayName = "SwipeableDrawer";
|
|
448
|
-
const cvaSwipeableDrawer = cssClassVarianceUtilities.cvaMerge([]
|
|
449
|
-
variants: {
|
|
450
|
-
docked: {
|
|
451
|
-
true: "translate-y-[calc(100%-30px)]",
|
|
452
|
-
false: "",
|
|
453
|
-
},
|
|
454
|
-
},
|
|
455
|
-
});
|
|
542
|
+
const cvaSwipeableDrawer = cssClassVarianceUtilities.cvaMerge([]);
|
|
456
543
|
|
|
457
544
|
/*
|
|
458
545
|
* ----------------------------
|
package/index.esm.js
CHANGED
|
@@ -96,7 +96,7 @@ const cvaOverlayContainer = cvaMerge([
|
|
|
96
96
|
variants: {
|
|
97
97
|
open: {
|
|
98
98
|
true: "z-popover opacity-100",
|
|
99
|
-
false: "z-
|
|
99
|
+
false: "z-hidden pointer-events-none opacity-0",
|
|
100
100
|
},
|
|
101
101
|
},
|
|
102
102
|
});
|
|
@@ -334,15 +334,19 @@ const getElementYTranslation = (element) => {
|
|
|
334
334
|
const matrix = new DOMMatrix(transform);
|
|
335
335
|
return matrix.m42;
|
|
336
336
|
};
|
|
337
|
+
const isPassedThreshold = (dragDistance, threshold) => {
|
|
338
|
+
return dragDistance >= threshold;
|
|
339
|
+
};
|
|
337
340
|
/**
|
|
338
341
|
* Hook for handling swipe events on the drawer.
|
|
339
342
|
*/
|
|
340
|
-
const useSwipeHandlers = ({ ref, closingThreshold, onCloseGesture, onOpenGesture }) => {
|
|
343
|
+
const useSwipeHandlers = ({ ref, closingThreshold, onCloseGesture, onOpenGesture, onDragEnd, }) => {
|
|
341
344
|
const [isDragging, setIsDragging] = useState(false);
|
|
342
345
|
const [dragDistance, setDragDistance] = useState(0);
|
|
343
346
|
const currentY = useRef(0);
|
|
344
347
|
const currentHeight = useRef(0);
|
|
345
348
|
const geometry = useGeometry(ref);
|
|
349
|
+
const threshold = currentHeight.current * closingThreshold;
|
|
346
350
|
const handlers = useSwipeable({
|
|
347
351
|
onTouchStartOrOnMouseDown: () => {
|
|
348
352
|
const y = getElementYTranslation(ref.current);
|
|
@@ -359,22 +363,22 @@ const useSwipeHandlers = ({ ref, closingThreshold, onCloseGesture, onOpenGesture
|
|
|
359
363
|
}
|
|
360
364
|
},
|
|
361
365
|
onSwipedDown: (e) => {
|
|
362
|
-
|
|
363
|
-
if (e.absY > threshold && onCloseGesture) {
|
|
366
|
+
if (isPassedThreshold(e.absY, threshold) && onCloseGesture) {
|
|
364
367
|
onCloseGesture();
|
|
365
368
|
}
|
|
366
369
|
setIsDragging(false);
|
|
367
370
|
setDragDistance(0);
|
|
368
371
|
},
|
|
369
372
|
onSwipedUp: (e) => {
|
|
370
|
-
|
|
371
|
-
if (e.absY > threshold && onOpenGesture) {
|
|
373
|
+
if (isPassedThreshold(e.absY, threshold) && onOpenGesture) {
|
|
372
374
|
onOpenGesture();
|
|
373
375
|
}
|
|
374
376
|
setIsDragging(false);
|
|
375
377
|
setDragDistance(0);
|
|
376
378
|
},
|
|
377
|
-
onSwiped: () =>
|
|
379
|
+
onSwiped: (e) => {
|
|
380
|
+
onDragEnd && onDragEnd(e.deltaY, e.velocity);
|
|
381
|
+
},
|
|
378
382
|
trackMouse: true,
|
|
379
383
|
trackTouch: true,
|
|
380
384
|
preventScrollOnSwipe: true,
|
|
@@ -398,6 +402,8 @@ const cvaPuller = cvaMerge(["pt-1", "pb-4", "flex", "items-center", "justify-cen
|
|
|
398
402
|
const cvaPullerIcon = cvaMerge(["block", "h-1", "w-8", "rounded-full", "bg-gray-400"]);
|
|
399
403
|
|
|
400
404
|
const CLOSING_THRESHOLD = 0.15;
|
|
405
|
+
const VELOCITY_THRESHOLD = 0.3;
|
|
406
|
+
const SNAP_POINT_TRANSITION = "transform 500ms cubic-bezier(0.32,0.72,0,1)";
|
|
401
407
|
/**
|
|
402
408
|
*
|
|
403
409
|
* SwipeableDrawer is a component that wraps the Drawer component to add swipeable functionality.
|
|
@@ -405,52 +411,133 @@ const CLOSING_THRESHOLD = 0.15;
|
|
|
405
411
|
* The component manages its docked state based on the open prop and keepMountedWhenClosed prop.
|
|
406
412
|
* It also applies styles dynamically based on whether the drawer is being dragged.
|
|
407
413
|
*/
|
|
408
|
-
const SwipeableDrawer = forwardRef(({ open, onClose, onOpenGesture, children, keepMountedWhenClosed, className, position = "bottom", ...others }, ref) => {
|
|
409
|
-
const
|
|
410
|
-
|
|
411
|
-
const
|
|
414
|
+
const SwipeableDrawer = forwardRef(({ open, onClose, onOpenGesture, onSnapPointChange, children, keepMountedWhenClosed, className, position = "bottom", snapPoints, activeSnapPoint: activeSnapPointProp = null, container, ...others }, ref) => {
|
|
415
|
+
const drawerRef = useRef(null);
|
|
416
|
+
const containerRef = useRef(container !== null && container !== void 0 ? container : null);
|
|
417
|
+
const containerGeometry = useGeometry(containerRef);
|
|
418
|
+
useImperativeHandle(ref, () => drawerRef.current);
|
|
419
|
+
const [activeSnapPoint, setActiveSnapPoint] = useState(activeSnapPointProp);
|
|
420
|
+
const [snapPointOffsets, setSnapPointOffsets] = useState([]);
|
|
421
|
+
const activeSnapPointIndex = useMemo(() => { var _a; return (_a = snapPoints === null || snapPoints === void 0 ? void 0 : snapPoints.findIndex(snapPoint => snapPoint === activeSnapPoint)) !== null && _a !== void 0 ? _a : -1; }, [snapPoints, activeSnapPoint]);
|
|
422
|
+
const onHandleDragEnd = useCallback((draggedBy, velocity) => {
|
|
423
|
+
var _a, _b, _c;
|
|
424
|
+
if (!snapPoints || !snapPoints.length) {
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
const direction = draggedBy > 0 ? -1 : 1;
|
|
428
|
+
// If the velocity is greater than the threshold and the dragged distance is less than 40% of the container height, snap to the next snap point
|
|
429
|
+
if (velocity > VELOCITY_THRESHOLD && Math.abs(draggedBy) < containerGeometry.height * 0.4) {
|
|
430
|
+
const newSnapPoint = (_a = snapPoints[activeSnapPointIndex + direction]) !== null && _a !== void 0 ? _a : null;
|
|
431
|
+
if (newSnapPoint) {
|
|
432
|
+
setActiveSnapPoint(newSnapPoint);
|
|
433
|
+
onSnapPointChange === null || onSnapPointChange === void 0 ? void 0 : onSnapPointChange(newSnapPoint);
|
|
434
|
+
}
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
// Otherwise, find the closest snap point
|
|
438
|
+
// This is when the user is dragging but not swiping.
|
|
439
|
+
// For example, when the user is dragging the drawer to the bottom but not fast enough to trigger a swipe.
|
|
440
|
+
const currentPosition = ((_b = snapPointOffsets[activeSnapPointIndex]) !== null && _b !== void 0 ? _b : 0) + draggedBy;
|
|
441
|
+
const closestSnapPoint = snapPointOffsets.reduce((prev, curr) => {
|
|
442
|
+
if (typeof prev !== "number" || typeof curr !== "number") {
|
|
443
|
+
return prev;
|
|
444
|
+
}
|
|
445
|
+
return Math.abs(curr - currentPosition) < Math.abs(prev - currentPosition) ? curr : prev;
|
|
446
|
+
});
|
|
447
|
+
const snapPointIndex = snapPointOffsets.findIndex(offset => offset === closestSnapPoint);
|
|
448
|
+
const nextSnapPoint = (_c = snapPoints[snapPointIndex]) !== null && _c !== void 0 ? _c : null;
|
|
449
|
+
if (!nextSnapPoint) {
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
setActiveSnapPoint(nextSnapPoint);
|
|
453
|
+
onSnapPointChange === null || onSnapPointChange === void 0 ? void 0 : onSnapPointChange(nextSnapPoint);
|
|
454
|
+
}, [snapPoints, snapPointOffsets, activeSnapPointIndex, containerGeometry, onSnapPointChange]);
|
|
412
455
|
const { handlers, isDragging, dragDistance } = useSwipeHandlers({
|
|
413
|
-
ref:
|
|
456
|
+
ref: drawerRef,
|
|
414
457
|
closingThreshold: CLOSING_THRESHOLD,
|
|
415
|
-
onCloseGesture:
|
|
416
|
-
|
|
458
|
+
onCloseGesture: () => {
|
|
459
|
+
if (!snapPoints) {
|
|
460
|
+
onClose && onClose();
|
|
461
|
+
}
|
|
462
|
+
},
|
|
463
|
+
onOpenGesture: () => {
|
|
464
|
+
if (!snapPoints) {
|
|
465
|
+
onOpenGesture && onOpenGesture();
|
|
466
|
+
}
|
|
467
|
+
},
|
|
468
|
+
onDragEnd: onHandleDragEnd,
|
|
417
469
|
});
|
|
418
470
|
useScrollBlock({ isDisabled: !open || isDragging });
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
transition: "none",
|
|
471
|
+
useEffect(() => {
|
|
472
|
+
if (!snapPoints || !drawerRef.current) {
|
|
473
|
+
return;
|
|
423
474
|
}
|
|
424
|
-
|
|
475
|
+
const containerSize = containerRef.current ? containerGeometry.height : window.innerHeight;
|
|
476
|
+
const newSnapPointOffsets = snapPoints.map(snapPoint => {
|
|
477
|
+
// If snapPoint is a number, it is a percentage of the container size
|
|
478
|
+
if (typeof snapPoint === "number") {
|
|
479
|
+
const height = Math.floor(containerSize * snapPoint);
|
|
480
|
+
return containerSize - height;
|
|
481
|
+
}
|
|
482
|
+
// If snapPoint is a string, it is a pixel value
|
|
483
|
+
if (snapPoint.endsWith("px")) {
|
|
484
|
+
return containerSize - parseInt(snapPoint.slice(0, -1), 10);
|
|
485
|
+
}
|
|
486
|
+
return 0;
|
|
487
|
+
});
|
|
488
|
+
setSnapPointOffsets(newSnapPointOffsets);
|
|
489
|
+
}, [snapPoints, drawerRef, containerRef, containerGeometry]);
|
|
490
|
+
useEffect(() => {
|
|
491
|
+
if (container) {
|
|
492
|
+
containerRef.current = container;
|
|
493
|
+
}
|
|
494
|
+
}, [container]);
|
|
425
495
|
useEffect(() => {
|
|
426
|
-
|
|
496
|
+
var _a;
|
|
497
|
+
if (!snapPoints || snapPoints.length === 0) {
|
|
427
498
|
return;
|
|
428
499
|
}
|
|
429
|
-
|
|
430
|
-
|
|
500
|
+
const snapPointIndex = snapPoints.findIndex(offset => offset === activeSnapPointProp);
|
|
501
|
+
const nextSnapPoint = (_a = snapPoints[snapPointIndex > -1 ? snapPointIndex : 0]) !== null && _a !== void 0 ? _a : null;
|
|
502
|
+
if (!nextSnapPoint) {
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
505
|
+
setActiveSnapPoint(nextSnapPoint);
|
|
506
|
+
onSnapPointChange === null || onSnapPointChange === void 0 ? void 0 : onSnapPointChange(nextSnapPoint);
|
|
507
|
+
}, [snapPoints, activeSnapPointProp, onSnapPointChange]);
|
|
508
|
+
const drawerStyle = useMemo(() => {
|
|
509
|
+
if (isDragging) {
|
|
510
|
+
return {
|
|
511
|
+
transform: `translateY(${dragDistance}px)`,
|
|
512
|
+
transition: "none",
|
|
513
|
+
};
|
|
431
514
|
}
|
|
432
|
-
|
|
433
|
-
|
|
515
|
+
if (snapPoints && snapPointOffsets.length > 0 && activeSnapPointIndex !== -1) {
|
|
516
|
+
return {
|
|
517
|
+
transform: `translateY(${snapPointOffsets[activeSnapPointIndex]}px)`,
|
|
518
|
+
transition: SNAP_POINT_TRANSITION,
|
|
519
|
+
};
|
|
434
520
|
}
|
|
435
|
-
|
|
521
|
+
return {};
|
|
522
|
+
}, [isDragging, dragDistance, snapPointOffsets, activeSnapPointIndex, snapPoints]);
|
|
436
523
|
useEffect(() => {
|
|
437
524
|
var _a, _b;
|
|
438
|
-
if (
|
|
439
|
-
|
|
440
|
-
drawerRefs.current.style.transition = (_b = drawerStyle.transition) !== null && _b !== void 0 ? _b : "";
|
|
525
|
+
if (!drawerRef.current) {
|
|
526
|
+
return;
|
|
441
527
|
}
|
|
442
|
-
|
|
443
|
-
|
|
528
|
+
if (open) {
|
|
529
|
+
drawerRef.current.style.transform = (_a = drawerStyle.transform) !== null && _a !== void 0 ? _a : "";
|
|
530
|
+
drawerRef.current.style.transition = (_b = drawerStyle.transition) !== null && _b !== void 0 ? _b : "";
|
|
531
|
+
}
|
|
532
|
+
else {
|
|
533
|
+
drawerRef.current.style.transform = "";
|
|
534
|
+
drawerRef.current.style.transition = "";
|
|
535
|
+
}
|
|
536
|
+
}, [drawerStyle, drawerRef, open]);
|
|
537
|
+
return (jsxs(Drawer, { className: cvaSwipeableDrawer({ className }), keepMountedWhenClosed: keepMountedWhenClosed, onClose: onClose, open: open, position: position, ref: drawerRef, ...others, children: [jsx(DrawerPuller, { ...handlers }), children] }));
|
|
444
538
|
});
|
|
445
539
|
SwipeableDrawer.displayName = "SwipeableDrawer";
|
|
446
|
-
const cvaSwipeableDrawer = cvaMerge([]
|
|
447
|
-
variants: {
|
|
448
|
-
docked: {
|
|
449
|
-
true: "translate-y-[calc(100%-30px)]",
|
|
450
|
-
false: "",
|
|
451
|
-
},
|
|
452
|
-
},
|
|
453
|
-
});
|
|
540
|
+
const cvaSwipeableDrawer = cvaMerge([]);
|
|
454
541
|
|
|
455
542
|
/*
|
|
456
543
|
* ----------------------------
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import { SwipeableDrawer } from "../SwipeableDrawer/SwipeableDrawer";
|
|
2
3
|
import { Drawer } from "./Drawer";
|
|
3
|
-
type Story = StoryObj<typeof Drawer>;
|
|
4
|
+
type Story = StoryObj<typeof Drawer | typeof SwipeableDrawer>;
|
|
4
5
|
declare const meta: Meta<typeof Drawer>;
|
|
5
6
|
export default meta;
|
|
6
7
|
export declare const packageName: () => import("react/jsx-runtime").JSX.Element;
|
|
@@ -9,4 +10,5 @@ export declare const InPortal: Story;
|
|
|
9
10
|
export declare const Toggleable: Story;
|
|
10
11
|
export declare const WithoutOverlay: Story;
|
|
11
12
|
export declare const Swipeable: Story;
|
|
12
|
-
export declare const
|
|
13
|
+
export declare const SwipeableWithSnapPoints: Story;
|
|
14
|
+
export declare const SwipeableWithSnapPointsWithoutContainer: Story;
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { type DrawerProps } from "../Drawer/Drawer";
|
|
2
2
|
export interface SwipeableDrawerProps extends DrawerProps {
|
|
3
3
|
onOpenGesture?: () => void;
|
|
4
|
+
onSnapPointChange?: (snapPoint: number | string) => void;
|
|
5
|
+
snapPoints?: (number | string)[];
|
|
6
|
+
activeSnapPoint?: number | string | null;
|
|
7
|
+
container?: HTMLElement | null;
|
|
4
8
|
}
|
|
5
9
|
/**
|
|
6
10
|
*
|
|
@@ -10,6 +14,4 @@ export interface SwipeableDrawerProps extends DrawerProps {
|
|
|
10
14
|
* It also applies styles dynamically based on whether the drawer is being dragged.
|
|
11
15
|
*/
|
|
12
16
|
export declare const SwipeableDrawer: import("react").ForwardRefExoticComponent<SwipeableDrawerProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
13
|
-
export declare const cvaSwipeableDrawer: (props?: (
|
|
14
|
-
docked?: boolean | null | undefined;
|
|
15
|
-
} & import("class-variance-authority/dist/types").ClassProp) | undefined) => string;
|
|
17
|
+
export declare const cvaSwipeableDrawer: (props?: import("class-variance-authority/dist/types").ClassProp | undefined) => string;
|
|
@@ -4,6 +4,7 @@ interface UseSwipeHandlersProps {
|
|
|
4
4
|
closingThreshold: number;
|
|
5
5
|
onCloseGesture?: () => void;
|
|
6
6
|
onOpenGesture?: () => void;
|
|
7
|
+
onDragEnd?: (dragDistance: number, velocity: number) => void;
|
|
7
8
|
}
|
|
8
9
|
/**
|
|
9
10
|
* Retrieves the Y-axis translation value of a given HTML element.
|
|
@@ -15,7 +16,7 @@ export declare const getElementYTranslation: (element?: HTMLElement | null) => n
|
|
|
15
16
|
/**
|
|
16
17
|
* Hook for handling swipe events on the drawer.
|
|
17
18
|
*/
|
|
18
|
-
export declare const useSwipeHandlers: ({ ref, closingThreshold, onCloseGesture, onOpenGesture }: UseSwipeHandlersProps) => {
|
|
19
|
+
export declare const useSwipeHandlers: ({ ref, closingThreshold, onCloseGesture, onOpenGesture, onDragEnd, }: UseSwipeHandlersProps) => {
|
|
19
20
|
handlers: import("react-swipeable").SwipeableHandlers;
|
|
20
21
|
isDragging: boolean;
|
|
21
22
|
dragDistance: number;
|