Skip to content

Commit 3e9673a

Browse files
authored
Merge pull request #25 from deco-sites/jonasjesus/deco-5284-drawer-swipe-close
feat(ui): swipe-to-close on Drawer (minicart + mobile menu) (DECO-5284)
2 parents a810bac + 0c6f510 commit 3e9673a

1 file changed

Lines changed: 44 additions & 1 deletion

File tree

src/components/ui/Drawer.tsx

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
import { useCallback, useId, type ReactNode } from "react";
1+
import {
2+
useCallback,
3+
useEffect,
4+
useId,
5+
useRef,
6+
type ReactNode,
7+
} from "react";
28
import { clx } from "~/sdk/clx";
39
import { useEscapeKey } from "../../sdk/useEscapeKey";
410
import Icon from "./Icon";
@@ -24,6 +30,42 @@ function Drawer(
2430

2531
useEscapeKey(closeViaCheckbox);
2632

33+
// Swipe-to-close: a horizontal swipe toward the edge the drawer opened from
34+
// closes it (the expected mobile gesture). Gesture-only — we let DaisyUI run
35+
// its own close transition rather than fighting it with a live drag transform.
36+
const asideRef = useRef<HTMLElement>(null);
37+
useEffect(() => {
38+
const aside = asideRef.current;
39+
const root = aside?.closest(".drawer");
40+
if (!aside || !root) return;
41+
const closesRightward = root.classList.contains("drawer-end");
42+
let startX = 0;
43+
let startY = 0;
44+
let tracking = false;
45+
const onDown = (e: PointerEvent) => {
46+
const input = document.getElementById(toggleId) as HTMLInputElement | null;
47+
if (!input?.checked) return; // only while open
48+
startX = e.clientX;
49+
startY = e.clientY;
50+
tracking = true;
51+
};
52+
const onUp = (e: PointerEvent) => {
53+
if (!tracking) return;
54+
tracking = false;
55+
const dx = e.clientX - startX;
56+
const dy = e.clientY - startY;
57+
// Require a clearly horizontal swipe so vertical scrolling is unaffected.
58+
if (Math.abs(dx) < 60 || Math.abs(dx) < Math.abs(dy)) return;
59+
if (closesRightward ? dx > 0 : dx < 0) closeViaCheckbox();
60+
};
61+
aside.addEventListener("pointerdown", onDown);
62+
aside.addEventListener("pointerup", onUp);
63+
return () => {
64+
aside.removeEventListener("pointerdown", onDown);
65+
aside.removeEventListener("pointerup", onUp);
66+
};
67+
}, [toggleId, closeViaCheckbox]);
68+
2769
return (
2870
<div className={clx("drawer", _class)}>
2971
<input
@@ -40,6 +82,7 @@ function Drawer(
4082
</div>
4183

4284
<aside
85+
ref={asideRef}
4386
data-aside
4487
className={clx(
4588
"drawer-side h-full z-40 overflow-hidden",

0 commit comments

Comments
 (0)