@@ -16,6 +16,7 @@ extend([mixPlugin]);
16
16
type GradientControlProps = {
17
17
gradient : ParsedGradient ;
18
18
onChange : ( value : ParsedGradient ) => void ;
19
+ onThumbSelected : ( index : number , stop : GradientStop ) => void ;
19
20
} ;
20
21
21
22
const defaultAngle : UnitValue = {
@@ -27,6 +28,7 @@ const defaultAngle: UnitValue = {
27
28
export const GradientControl = ( props : GradientControlProps ) => {
28
29
const [ stops , setStops ] = useState < Array < GradientStop > > ( props . gradient . stops ) ;
29
30
const [ selectedStop , setSelectedStop ] = useState < number | undefined > ( ) ;
31
+ const [ isHoveredOnStop , setIsHoveredOnStop ] = useState < boolean > ( false ) ;
30
32
const positions = stops
31
33
. map ( ( stop ) => stop . position ?. value )
32
34
. filter ( ( item ) => item !== undefined ) ;
@@ -80,24 +82,36 @@ export const GradientControl = (props: GradientControlProps) => {
80
82
[ stops , selectedStop ]
81
83
) ;
82
84
83
- const handlePointerDown = useCallback (
84
- ( event : React . MouseEvent < HTMLSpanElement > ) => {
85
- if ( event . target === undefined || event . target === null ) {
86
- return ;
87
- }
88
-
89
- // radix-slider automatically brings the closest thumb to the clicked position.
90
- // But, we want it be prevented. So, we can add a new color-stop where the user is cliked.
91
- // And handle the even for scrubing when the user is dragging the thumb.
85
+ const isStopExistsAtPosition = useCallback (
86
+ (
87
+ event : React . MouseEvent < HTMLSpanElement >
88
+ ) : { isStopExistingAtPosition : boolean ; newPosition : number } => {
92
89
const sliderWidth = event . currentTarget . offsetWidth ;
93
90
const clickedPosition =
94
91
event . clientX - event . currentTarget . getBoundingClientRect ( ) . left ;
95
92
const newPosition = Math . ceil ( ( clickedPosition / sliderWidth ) * 100 ) ;
96
- const isExistingPosition = positions . some (
93
+ // The 8px buffer here is the width of the thumb. We don't want to add a new stop if the user clicks on the thumb.
94
+ const isStopExistingAtPosition = positions . some (
97
95
( position ) => Math . abs ( newPosition - position ) <= 8
98
96
) ;
99
97
100
- if ( isExistingPosition === true ) {
98
+ return { isStopExistingAtPosition, newPosition } ;
99
+ } ,
100
+ [ positions ]
101
+ ) ;
102
+
103
+ const handlePointerDown = useCallback (
104
+ ( event : React . MouseEvent < HTMLSpanElement > ) => {
105
+ if ( event . target === undefined || event . target === null ) {
106
+ return ;
107
+ }
108
+
109
+ // radix-slider automatically brings the closest thumb to the clicked position.
110
+ // But, we want it be prevented. For adding a new color-stop where the user clicked.
111
+ // And handle the change in values only even for scrubing when the user is dragging the thumb.
112
+ const { isStopExistingAtPosition, newPosition } =
113
+ isStopExistsAtPosition ( event ) ;
114
+ if ( isStopExistingAtPosition === true ) {
101
115
return ;
102
116
}
103
117
@@ -131,11 +145,23 @@ export const GradientControl = (props: GradientControlProps) => {
131
145
} ,
132
146
...stops . slice ( index ) ,
133
147
] ;
148
+
134
149
setStops ( newStops ) ;
150
+ setIsHoveredOnStop ( true ) ;
151
+ props . onChange ( {
152
+ angle : props . gradient . angle ,
153
+ stops : newStops ,
154
+ sideOrCorner : props . gradient . sideOrCorner ,
155
+ } ) ;
135
156
} ,
136
- [ stops , positions ]
157
+ [ stops , positions , isStopExistsAtPosition , props ]
137
158
) ;
138
159
160
+ const handleMouseEnter = ( event : React . MouseEvent < HTMLSpanElement > ) => {
161
+ const { isStopExistingAtPosition } = isStopExistsAtPosition ( event ) ;
162
+ setIsHoveredOnStop ( isStopExistingAtPosition ) ;
163
+ } ;
164
+
139
165
if ( isEveryStopHasAPosition === false ) {
140
166
return ;
141
167
}
@@ -156,15 +182,22 @@ export const GradientControl = (props: GradientControlProps) => {
156
182
onValueChange = { handleValueChange }
157
183
onKeyDown = { handleKeyDown }
158
184
onPointerDown = { handlePointerDown }
185
+ isHoveredOnStop = { isHoveredOnStop }
186
+ onMouseEnter = { handleMouseEnter }
187
+ onMouseMove = { handleMouseEnter }
188
+ onMouseLeave = { ( ) => {
189
+ setIsHoveredOnStop ( false ) ;
190
+ } }
159
191
>
160
192
< Track >
161
- < SliderRange css = { { cursor : "copy" } } />
193
+ < SliderRange />
162
194
</ Track >
163
195
{ stops . map ( ( stop , index ) => (
164
196
< SliderThumb
165
197
key = { index }
166
198
onClick = { ( ) => {
167
199
setSelectedStop ( index ) ;
200
+ props . onThumbSelected ( index , stop ) ;
168
201
} }
169
202
style = { {
170
203
background : toValue ( stop . color ) ,
@@ -211,6 +244,16 @@ const SliderRoot = styled(Root, {
211
244
borderRadius : theme . borderRadius [ 3 ] ,
212
245
touchAction : "none" ,
213
246
userSelect : "none" ,
247
+ variants : {
248
+ isHoveredOnStop : {
249
+ true : {
250
+ cursor : "default" ,
251
+ } ,
252
+ false : {
253
+ cursor : "copy" ,
254
+ } ,
255
+ } ,
256
+ } ,
214
257
} ) ;
215
258
216
259
const SliderRange = styled ( Range , {
0 commit comments