Skip to content

Commit c1eb709

Browse files
committed
fix horizontal scroll
1 parent 6c11d8c commit c1eb709

File tree

1 file changed

+65
-2
lines changed

1 file changed

+65
-2
lines changed

src/components/Controls.tsx

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { ReactNode } from "react";
22

33
import { Box, ButtonBase, Typography } from "@mui/material";
4+
import { useRef, useEffect } from "react";
45

56
import WavesIcon from "@mui/icons-material/ShowChart";
67
import MediaIcon from "@mui/icons-material/AudioFile";
@@ -69,8 +70,63 @@ type ControlsProps = {
6970
};
7071

7172
export function Controls({ section, setSection }: ControlsProps) {
73+
// Small passive touch-listener detects horizontal drags so clicks are suppressed
74+
// while a user is scrolling the bar. This keeps native scrolling behavior and
75+
// avoids reimplementing drag-to-scroll.
76+
const containerRef = useRef<HTMLDivElement | null>(null);
77+
const isDraggingRef = useRef(false);
78+
79+
useEffect(() => {
80+
const el = containerRef.current;
81+
if (!el) return;
82+
83+
let startX = 0;
84+
const THRESHOLD = 6; // pixels
85+
86+
const onTouchStart = (e: TouchEvent) => {
87+
if (e.touches.length !== 1) return;
88+
startX = e.touches[0].clientX;
89+
isDraggingRef.current = false;
90+
};
91+
92+
const onTouchMove = (e: TouchEvent) => {
93+
if (e.touches.length !== 1) return;
94+
const dx = e.touches[0].clientX - startX;
95+
if (Math.abs(dx) > THRESHOLD) {
96+
isDraggingRef.current = true;
97+
}
98+
};
99+
100+
const onTouchEnd = () => {
101+
// small delay so immediate click after lift doesn't fire
102+
setTimeout(() => {
103+
isDraggingRef.current = false;
104+
}, 50);
105+
};
106+
107+
el.addEventListener("touchstart", onTouchStart, { passive: true });
108+
el.addEventListener("touchmove", onTouchMove, { passive: true });
109+
el.addEventListener("touchend", onTouchEnd, { passive: true });
110+
111+
return () => {
112+
el.removeEventListener("touchstart", onTouchStart as EventListener);
113+
el.removeEventListener("touchmove", onTouchMove as EventListener);
114+
el.removeEventListener("touchend", onTouchEnd as EventListener);
115+
};
116+
}, []);
117+
118+
const onItemClick = (key: ControlKey, e: React.MouseEvent) => {
119+
if (isDraggingRef.current) {
120+
e.preventDefault();
121+
e.stopPropagation();
122+
return;
123+
}
124+
setSection(key);
125+
};
126+
72127
return (
73128
<Box
129+
ref={containerRef}
74130
sx={{
75131
display: "flex",
76132
flexDirection: { xs: "row", md: "column" },
@@ -82,12 +138,17 @@ export function Controls({ section, setSection }: ControlsProps) {
82138
height: "100%",
83139
// enable smooth momentum scrolling on iOS
84140
WebkitOverflowScrolling: "touch",
85-
// prefer horizontal pan on touch devices
141+
// let the browser handle horizontal panning natively
86142
touchAction: { xs: "pan-x", md: "auto" },
87143
// enable scroll snap on mobile so items align nicely and scrolling feels natural
88144
scrollSnapType: { xs: "x mandatory", md: "none" },
89145
// give a bit of horizontal padding so items aren't flush to the edge
90146
px: { xs: 1, md: 0 },
147+
// prevent text selection while dragging
148+
userSelect: { xs: "none", md: "auto" },
149+
WebkitUserSelect: { xs: "none", md: "auto" },
150+
// keep overscroll within the container to avoid parent scrolling
151+
overscrollBehavior: { xs: "contain", md: "auto" },
91152
// hide the default webkit scrollbar for cleaner look on mobile
92153
"&::-webkit-scrollbar": { display: { xs: "none", md: "block" } },
93154
}}
@@ -98,7 +159,7 @@ export function Controls({ section, setSection }: ControlsProps) {
98159
return (
99160
<ButtonBase
100161
key={item.key}
101-
onClick={() => setSection(item.key)}
162+
onClick={(e) => onItemClick(item.key, e)}
102163
aria-pressed={isActive}
103164
sx={{
104165
display: "flex",
@@ -116,6 +177,8 @@ export function Controls({ section, setSection }: ControlsProps) {
116177
flexShrink: { xs: 0, md: 1 },
117178
scrollSnapAlign: { xs: "center", md: "none" },
118179
minWidth: { xs: 88, md: "auto" },
180+
// let touches on buttons still allow native pan gestures
181+
touchAction: { xs: "manipulation", md: "auto" },
119182
bgcolor: isActive
120183
? { xs: "transparent", md: "action.selected" }
121184
: undefined,

0 commit comments

Comments
 (0)