1
- import React , { useEffect , useState } from 'react' ;
1
+ import React , { useEffect , useRef , useState } from 'react' ;
2
2
import {
3
3
FaInbox ,
4
4
FaFileExcel ,
@@ -14,85 +14,85 @@ import excelclipGif from './gifs/excelclip.gif';
14
14
import essayclipGif from './gifs/essayclip.gif' ;
15
15
import lunchclipGif from './gifs/lunchclip.gif' ;
16
16
import figmaclipGif from './gifs/figmaclip.gif' ;
17
- import jobclipGif from './gifs/jobclip.gif' ;
17
+ import jobclipGif from './gifs/jobclip.gif' ;
18
18
19
19
const gifMap = {
20
20
'inboxclips.gif' : inboxclipGif ,
21
- 'excelclip.gif' : excelclipGif ,
22
- 'essayclip.gif' : essayclipGif ,
23
- 'lunchclip.gif' : lunchclipGif ,
24
- 'figmaclip.gif' : figmaclipGif ,
25
- 'jobclip.gif' : jobclipGif ,
21
+ 'excelclip.gif' : excelclipGif ,
22
+ 'essayclip.gif' : essayclipGif ,
23
+ 'lunchclip.gif' : lunchclipGif ,
24
+ 'figmaclip.gif' : figmaclipGif ,
25
+ 'jobclip.gif' : jobclipGif ,
26
26
} ;
27
27
28
28
const sliderIcons = [
29
- { icon : FaInbox , value : 1 , key : 'inbox' } ,
30
- { icon : FaFileExcel , value : 2 , key : 'excel' } ,
31
- { icon : FaPencilAlt , value : 3 , key : 'essay' } ,
32
- { icon : FaUtensils , value : 4 , key : 'lunch' } ,
29
+ { icon : FaInbox , value : 1 , key : 'inbox' } ,
30
+ { icon : FaFileExcel , value : 2 , key : 'excel' } ,
31
+ { icon : FaPencilAlt , value : 3 , key : 'essay' } ,
32
+ { icon : FaUtensils , value : 4 , key : 'lunch' } ,
33
33
{ icon : FaPaintBrush , value : 5 , key : 'figma' } ,
34
- { icon : FaBriefcase , value : 6 , key : 'job' } ,
34
+ { icon : FaBriefcase , value : 6 , key : 'job' } ,
35
35
] ;
36
36
37
37
/* ──────── Slider ──────── */
38
38
function FancySlider ( { min, max, step, value, onChange, icons } ) {
39
- const sliderRef = React . useRef ( null ) ;
40
- const [ isDragging , setIsDragging ] = useState ( false ) ;
39
+ const trackRef = useRef ( null ) ;
40
+ const [ dragging , setDragging ] = useState ( false ) ;
41
41
42
+ /* -------- pointer handlers -------- */
42
43
useEffect ( ( ) => {
43
- const handleMove = ( clientX ) => {
44
- if ( ! isDragging || ! sliderRef . current ) return ;
45
- const { left, width } = sliderRef . current . getBoundingClientRect ( ) ;
46
- const clampedX = Math . max ( 0 , Math . min ( clientX - left , width ) ) ;
47
- const ratio = clampedX / width ;
48
- const newValue =
49
- Math . round ( ( min + ratio * ( max - min ) ) / step ) * step ;
50
- onChange ( newValue ) ;
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 ) ;
51
53
} ;
52
54
53
- const mouse = ( e ) => handleMove ( e . clientX ) ;
54
-
55
- const touch = ( e ) => {
56
- if ( isDragging ) e . preventDefault ( ) ; // prevent pull-to-refresh
57
- if ( e . touches [ 0 ] ) handleMove ( e . touches [ 0 ] . clientX ) ;
55
+ const handlePointerMove = ( e ) => {
56
+ if ( ! dragging ) return ;
57
+ e . preventDefault ( ) ;
58
+ move ( e . clientX ) ;
58
59
} ;
59
60
60
- const endDrag = ( ) => setIsDragging ( false ) ;
61
+ const handlePointerUp = ( ) => setDragging ( false ) ;
61
62
62
- window . addEventListener ( 'mousemove' , mouse ) ;
63
- window . addEventListener ( 'mouseup' , endDrag ) ;
64
- // ⚠️ non-passive listener so preventDefault works on iOS
65
- window . addEventListener ( 'touchmove' , touch , { passive : false } ) ;
66
- window . addEventListener ( 'touchend' , endDrag ) ;
67
- window . addEventListener ( 'touchcancel' , endDrag ) ;
63
+ window . addEventListener ( 'pointermove' , handlePointerMove ) ;
64
+ window . addEventListener ( 'pointerup' , handlePointerUp ) ;
68
65
69
66
return ( ) => {
70
- window . removeEventListener ( 'mousemove' , mouse ) ;
71
- window . removeEventListener ( 'mouseup' , endDrag ) ;
72
- window . removeEventListener ( 'touchmove' , touch , { passive : false } ) ;
73
- window . removeEventListener ( 'touchend' , endDrag ) ;
74
- window . removeEventListener ( 'touchcancel' , endDrag ) ;
67
+ window . removeEventListener ( 'pointermove' , handlePointerMove ) ;
68
+ window . removeEventListener ( 'pointerup' , handlePointerUp ) ;
75
69
} ;
76
- } , [ isDragging , min , max , step , onChange ] ) ;
70
+ } , [ dragging , min , max , step , onChange , value ] ) ;
77
71
78
- const ratio = ( value - min ) / ( max - min ) ;
79
- const iconSize = 20 ; // Size of the icon for positioning calculations
72
+ /* -------- slider visuals -------- */
73
+ const ratio = ( value - min ) / ( max - min ) ;
74
+ const iconSize = 20 ;
80
75
81
76
return (
82
77
< div
83
- style = { {
84
- position : 'relative' ,
85
- width : '100%' ,
86
- height : 40 /* Increased height for icons */ ,
87
- } }
78
+ style = { { position : 'relative' , width : '100%' , height : 40 } }
88
79
>
80
+ { /* TRACK */ }
89
81
< div
90
- ref = { sliderRef }
82
+ ref = { trackRef }
91
83
style = { {
92
84
position : 'relative' ,
93
85
width : '100%' ,
94
86
height : 20 ,
95
- marginTop : 15 /* Make space for icons above */ ,
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 ) ;
96
96
} }
97
97
>
98
98
< div
@@ -120,11 +120,6 @@ function FancySlider({ min, max, step, value, onChange, icons }) {
120
120
} }
121
121
/>
122
122
< div
123
- onMouseDown = { ( e ) => {
124
- e . preventDefault ( ) ;
125
- setIsDragging ( true ) ;
126
- } }
127
- onTouchStart = { ( ) => setIsDragging ( true ) }
128
123
style = { {
129
124
position : 'absolute' ,
130
125
top : '50%' ,
@@ -139,41 +134,41 @@ function FancySlider({ min, max, step, value, onChange, icons }) {
139
134
} }
140
135
/>
141
136
</ div >
137
+
138
+ { /* ICONS */ }
142
139
< div
143
140
style = { {
144
141
display : 'flex' ,
145
142
justifyContent : 'space-between' ,
146
143
width : '100%' ,
147
144
position : 'absolute' ,
148
145
top : 0 ,
146
+ pointerEvents : 'none' , // icons themselves don’t intercept drag
149
147
} }
150
148
>
151
- { icons . map ( ( iconData ) => {
152
- const IconComponent = iconData . icon ;
153
- const iconRatio = ( iconData . value - min ) / ( max - min ) ;
154
- const isActive = iconData . value === value ;
149
+ { icons . map ( ( { icon : Icon , value : v , key } ) => {
150
+ const iconRatio = ( v - min ) / ( max - min ) ;
151
+ const active = v === value ;
155
152
return (
156
153
< div
157
- key = { iconData . key }
158
- onClick = { ( ) => onChange ( iconData . value ) }
154
+ key = { key }
155
+ onPointerDown = { ( e ) => { // allow tap-to-jump
156
+ e . preventDefault ( ) ;
157
+ onChange ( v ) ;
158
+ } }
159
159
style = { {
160
160
position : 'absolute' ,
161
161
left : `calc(${ iconRatio * 100 } % - ${ iconSize / 2 } px)` ,
162
162
top : '50%' ,
163
163
transform : 'translateY(-50%)' ,
164
+ fontSize : iconSize ,
165
+ color : active ? '#d6ceba' : 'rgba(214,206,186,.5)' ,
164
166
cursor : 'pointer' ,
165
- zIndex : 1 ,
166
- color : isActive
167
- ? '#d6ceba'
168
- : 'rgba(214,206,186,.5)' ,
169
- fontSize : `${ iconSize } px` ,
167
+ pointerEvents : 'auto' ,
170
168
} }
171
- title = {
172
- iconData . key . charAt ( 0 ) . toUpperCase ( ) +
173
- iconData . key . slice ( 1 )
174
- } // Tooltip for accessibility
169
+ title = { key . charAt ( 0 ) . toUpperCase ( ) + key . slice ( 1 ) }
175
170
>
176
- < IconComponent />
171
+ < Icon />
177
172
</ div >
178
173
) ;
179
174
} ) }
@@ -184,7 +179,7 @@ function FancySlider({ min, max, step, value, onChange, icons }) {
184
179
185
180
/* ──────── Left Pane ──────── */
186
181
const LeftPane = ( { selectedHour, onTimeChange, activity, gif } ) => {
187
- const gifSrc = gifMap [ gif ] || inboxclipGif ; // fallback to inbox clip
182
+ const gifSrc = gifMap [ gif ] || inboxclipGif ; // fallback
188
183
189
184
return (
190
185
< div className = "leftpane-container" >
@@ -201,25 +196,21 @@ const LeftPane = ({ selectedHour, onTimeChange, activity, gif }) => {
201
196
margin : '0 auto' ,
202
197
} }
203
198
/>
204
- < p style = { { margin : '15px 0 15px ' , fontSize : 16 } } >
199
+ < p style = { { margin : '15px 0' , fontSize : 16 } } >
205
200
< b > { activity } </ b >
206
201
</ p >
207
202
</ div >
208
203
209
204
{ /* Hour selector */ }
210
205
< div style = { { width : 200 , margin : '0 auto' } } >
211
- < div
212
- style = { { display : 'flex' , alignItems : 'center' , gap : 20 } }
213
- >
214
- < FancySlider
215
- min = { 1 }
216
- max = { 6 }
217
- step = { 1 }
218
- value = { selectedHour }
219
- onChange = { onTimeChange }
220
- icons = { sliderIcons }
221
- />
222
- </ div >
206
+ < FancySlider
207
+ min = { 1 }
208
+ max = { 6 }
209
+ step = { 1 }
210
+ value = { selectedHour }
211
+ onChange = { onTimeChange }
212
+ icons = { sliderIcons }
213
+ />
223
214
</ div >
224
215
</ div >
225
216
) ;
0 commit comments