1- import React , { useEffect , useState } from 'react' ;
1+ import React , { useEffect , useRef , useState } from 'react' ;
22import {
33 FaInbox ,
44 FaFileExcel ,
@@ -14,85 +14,85 @@ import excelclipGif from './gifs/excelclip.gif';
1414import essayclipGif from './gifs/essayclip.gif' ;
1515import lunchclipGif from './gifs/lunchclip.gif' ;
1616import figmaclipGif from './gifs/figmaclip.gif' ;
17- import jobclipGif from './gifs/jobclip.gif' ;
17+ import jobclipGif from './gifs/jobclip.gif' ;
1818
1919const gifMap = {
2020 '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 ,
2626} ;
2727
2828const 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' } ,
3333 { icon : FaPaintBrush , value : 5 , key : 'figma' } ,
34- { icon : FaBriefcase , value : 6 , key : 'job' } ,
34+ { icon : FaBriefcase , value : 6 , key : 'job' } ,
3535] ;
3636
3737/* ──────── Slider ──────── */
3838function 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 ) ;
4141
42+ /* -------- pointer handlers -------- */
4243 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 ) ;
5153 } ;
5254
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 ) ;
5859 } ;
5960
60- const endDrag = ( ) => setIsDragging ( false ) ;
61+ const handlePointerUp = ( ) => setDragging ( false ) ;
6162
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 ) ;
6865
6966 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 ) ;
7569 } ;
76- } , [ isDragging , min , max , step , onChange ] ) ;
70+ } , [ dragging , min , max , step , onChange , value ] ) ;
7771
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 ;
8075
8176 return (
8277 < 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 } }
8879 >
80+ { /* TRACK */ }
8981 < div
90- ref = { sliderRef }
82+ ref = { trackRef }
9183 style = { {
9284 position : 'relative' ,
9385 width : '100%' ,
9486 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 ) ;
9696 } }
9797 >
9898 < div
@@ -120,11 +120,6 @@ function FancySlider({ min, max, step, value, onChange, icons }) {
120120 } }
121121 />
122122 < div
123- onMouseDown = { ( e ) => {
124- e . preventDefault ( ) ;
125- setIsDragging ( true ) ;
126- } }
127- onTouchStart = { ( ) => setIsDragging ( true ) }
128123 style = { {
129124 position : 'absolute' ,
130125 top : '50%' ,
@@ -139,41 +134,41 @@ function FancySlider({ min, max, step, value, onChange, icons }) {
139134 } }
140135 />
141136 </ div >
137+
138+ { /* ICONS */ }
142139 < div
143140 style = { {
144141 display : 'flex' ,
145142 justifyContent : 'space-between' ,
146143 width : '100%' ,
147144 position : 'absolute' ,
148145 top : 0 ,
146+ pointerEvents : 'none' , // icons themselves don’t intercept drag
149147 } }
150148 >
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 ;
155152 return (
156153 < 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+ } }
159159 style = { {
160160 position : 'absolute' ,
161161 left : `calc(${ iconRatio * 100 } % - ${ iconSize / 2 } px)` ,
162162 top : '50%' ,
163163 transform : 'translateY(-50%)' ,
164+ fontSize : iconSize ,
165+ color : active ? '#d6ceba' : 'rgba(214,206,186,.5)' ,
164166 cursor : 'pointer' ,
165- zIndex : 1 ,
166- color : isActive
167- ? '#d6ceba'
168- : 'rgba(214,206,186,.5)' ,
169- fontSize : `${ iconSize } px` ,
167+ pointerEvents : 'auto' ,
170168 } }
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 ) }
175170 >
176- < IconComponent />
171+ < Icon />
177172 </ div >
178173 ) ;
179174 } ) }
@@ -184,7 +179,7 @@ function FancySlider({ min, max, step, value, onChange, icons }) {
184179
185180/* ──────── Left Pane ──────── */
186181const LeftPane = ( { selectedHour, onTimeChange, activity, gif } ) => {
187- const gifSrc = gifMap [ gif ] || inboxclipGif ; // fallback to inbox clip
182+ const gifSrc = gifMap [ gif ] || inboxclipGif ; // fallback
188183
189184 return (
190185 < div className = "leftpane-container" >
@@ -201,25 +196,21 @@ const LeftPane = ({ selectedHour, onTimeChange, activity, gif }) => {
201196 margin : '0 auto' ,
202197 } }
203198 />
204- < p style = { { margin : '15px 0 15px ' , fontSize : 16 } } >
199+ < p style = { { margin : '15px 0' , fontSize : 16 } } >
205200 < b > { activity } </ b >
206201 </ p >
207202 </ div >
208203
209204 { /* Hour selector */ }
210205 < 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+ />
223214 </ div >
224215 </ div >
225216 ) ;
0 commit comments