Skip to content

Commit 7a52981

Browse files
committed
try 3
1 parent eff33b2 commit 7a52981

File tree

1 file changed

+78
-58
lines changed

1 file changed

+78
-58
lines changed

src/components/LeftPane.jsx

Lines changed: 78 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -36,63 +36,78 @@ const sliderIcons = [
3636

3737
/* ──────── Slider ──────── */
3838
function FancySlider({ min, max, step, value, onChange, icons }) {
39-
const trackRef = useRef(null);
40-
const [dragging, setDragging] = useState(false);
39+
const sliderRef = React.useRef(null);
40+
const [isDragging, setIsDragging] = useState(false);
41+
const lastUpdateRef = useRef(0); // throttle redraws
42+
const THROTTLE_MS = 120; // ~8 fps is plenty
4143

42-
/* -------- pointer handlers -------- */
4344
useEffect(() => {
44-
const track = trackRef.current;
45-
if (!track) return;
46-
47-
const move = (clientX) => {
48-
const { left, width } = track.getBoundingClientRect();
49-
const clamped = Math.max(0, Math.min(clientX - left, width));
50-
const ratio = clamped / width;
51-
const newVal = Math.round((min + ratio * (max - min)) / step) * step;
52-
if (newVal !== value) onChange(newVal);
45+
/* ─── helper that *may* trigger parent update ─── */
46+
const maybeUpdate = (newVal, force = false) => {
47+
if (newVal === value) return; // no change – skip
48+
const now = Date.now();
49+
if (force || now - lastUpdateRef.current > THROTTLE_MS) {
50+
lastUpdateRef.current = now;
51+
onChange(newVal);
52+
}
5353
};
5454

55-
const handlePointerMove = (e) => {
56-
if (!dragging) return;
57-
e.preventDefault();
58-
move(e.clientX);
55+
const handleMove = (clientX) => {
56+
if (!isDragging || !sliderRef.current) return;
57+
const { left, width } = sliderRef.current.getBoundingClientRect();
58+
const clampedX = Math.max(0, Math.min(clientX - left, width));
59+
const ratio = clampedX / width;
60+
const newValue = Math.round((min + ratio * (max - min)) / step) * step;
61+
maybeUpdate(newValue); // throttled
5962
};
6063

61-
const handlePointerUp = () => setDragging(false);
64+
const mouse = (e) => handleMove(e.clientX);
65+
const touch = (e) => {
66+
if (isDragging) e.preventDefault(); // block pull-to-refresh
67+
if (e.touches[0]) handleMove(e.touches[0].clientX);
68+
};
69+
const endDrag = (e) => {
70+
if (!sliderRef.current) return;
71+
/* ensure *one* final update with the exact position */
72+
const finalX = e.changedTouches?.[0]?.clientX ?? e.clientX;
73+
handleMove(finalX);
74+
maybeUpdate(value, true /*force*/);
75+
setIsDragging(false);
76+
};
6277

63-
window.addEventListener('pointermove', handlePointerMove);
64-
window.addEventListener('pointerup', handlePointerUp);
78+
window.addEventListener('mousemove', mouse);
79+
window.addEventListener('mouseup', endDrag);
80+
window.addEventListener('touchmove', touch, { passive: false });
81+
window.addEventListener('touchend', endDrag);
82+
window.addEventListener('touchcancel', endDrag);
6583

6684
return () => {
67-
window.removeEventListener('pointermove', handlePointerMove);
68-
window.removeEventListener('pointerup', handlePointerUp);
85+
window.removeEventListener('mousemove', mouse);
86+
window.removeEventListener('mouseup', endDrag);
87+
window.removeEventListener('touchmove', touch, { passive: false });
88+
window.removeEventListener('touchend', endDrag);
89+
window.removeEventListener('touchcancel', endDrag);
6990
};
70-
}, [dragging, min, max, step, onChange, value]);
91+
}, [isDragging, min, max, step, value, onChange]);
7192

72-
/* -------- slider visuals -------- */
73-
const ratio = (value - min) / (max - min);
74-
const iconSize = 20;
93+
const ratio = (value - min) / (max - min);
94+
const iconSize = 20; // icon sizing unchanged
7595

7696
return (
7797
<div
78-
style={{ position: 'relative', width: '100%', height: 40 }}
98+
style={{
99+
position: 'relative',
100+
width: '100%',
101+
height: 40, // leave room for icons (unchanged)
102+
}}
79103
>
80-
{/* TRACK */}
81104
<div
82-
ref={trackRef}
105+
ref={sliderRef}
83106
style={{
84107
position: 'relative',
85108
width: '100%',
86109
height: 20,
87-
marginTop: 15,
88-
touchAction: 'none', // disable browser gestures
89-
overscrollBehaviorY: 'contain'
90-
}}
91-
onPointerDown={(e) => {
92-
setDragging(true);
93-
e.target.setPointerCapture(e.pointerId);
94-
e.preventDefault();
95-
move(e.clientX);
110+
marginTop: 15, // same 15 px offset as original
96111
}}
97112
>
98113
<div
@@ -120,6 +135,11 @@ function FancySlider({ min, max, step, value, onChange, icons }) {
120135
}}
121136
/>
122137
<div
138+
onMouseDown={(e) => {
139+
e.preventDefault();
140+
setIsDragging(true);
141+
}}
142+
onTouchStart={() => setIsDragging(true)}
123143
style={{
124144
position: 'absolute',
125145
top: '50%',
@@ -135,40 +155,38 @@ function FancySlider({ min, max, step, value, onChange, icons }) {
135155
/>
136156
</div>
137157

138-
{/* ICONS */}
158+
{/* icon row – absolutely identical coordinates */}
139159
<div
140160
style={{
141161
display: 'flex',
142162
justifyContent: 'space-between',
143163
width: '100%',
144164
position: 'absolute',
145165
top: 0,
146-
pointerEvents: 'none', // icons themselves don’t intercept drag
147166
}}
148167
>
149-
{icons.map(({ icon: Icon, value: v, key }) => {
168+
{icons.map(({ icon: IconComponent, value: v, key }) => {
150169
const iconRatio = (v - min) / (max - min);
151-
const active = v === value;
170+
const isActive = v === value;
152171
return (
153172
<div
154173
key={key}
155-
onPointerDown={(e) => { // allow tap-to-jump
156-
e.preventDefault();
157-
onChange(v);
158-
}}
174+
onClick={() => onChange(v)}
159175
style={{
160176
position: 'absolute',
161177
left: `calc(${iconRatio * 100}% - ${iconSize / 2}px)`,
162178
top: '50%',
163179
transform: 'translateY(-50%)',
164-
fontSize: iconSize,
165-
color: active ? '#d6ceba' : 'rgba(214,206,186,.5)',
166180
cursor: 'pointer',
167-
pointerEvents: 'auto',
181+
zIndex: 1,
182+
color: isActive
183+
? '#d6ceba'
184+
: 'rgba(214,206,186,.5)',
185+
fontSize: `${iconSize}px`,
168186
}}
169187
title={key.charAt(0).toUpperCase() + key.slice(1)}
170188
>
171-
<Icon />
189+
<IconComponent />
172190
</div>
173191
);
174192
})}
@@ -179,7 +197,7 @@ function FancySlider({ min, max, step, value, onChange, icons }) {
179197

180198
/* ──────── Left Pane ──────── */
181199
const LeftPane = ({ selectedHour, onTimeChange, activity, gif }) => {
182-
const gifSrc = gifMap[gif] || inboxclipGif; // fallback
200+
const gifSrc = gifMap[gif] || inboxclipGif; // fallback gif
183201

184202
return (
185203
<div className="leftpane-container">
@@ -203,14 +221,16 @@ const LeftPane = ({ selectedHour, onTimeChange, activity, gif }) => {
203221

204222
{/* Hour selector */}
205223
<div style={{ width: 200, margin: '0 auto' }}>
206-
<FancySlider
207-
min={1}
208-
max={6}
209-
step={1}
210-
value={selectedHour}
211-
onChange={onTimeChange}
212-
icons={sliderIcons}
213-
/>
224+
<div style={{ display: 'flex', alignItems: 'center', gap: 20 }}>
225+
<FancySlider
226+
min={1}
227+
max={6}
228+
step={1}
229+
value={selectedHour}
230+
onChange={onTimeChange}
231+
icons={sliderIcons}
232+
/>
233+
</div>
214234
</div>
215235
</div>
216236
);

0 commit comments

Comments
 (0)