@@ -16,6 +16,7 @@ extend([mixPlugin]);
1616type GradientControlProps = {
1717 gradient : ParsedGradient ;
1818 onChange : ( value : ParsedGradient ) => void ;
19+ onThumbSelected : ( index : number , stop : GradientStop ) => void ;
1920} ;
2021
2122const defaultAngle : UnitValue = {
@@ -27,6 +28,7 @@ const defaultAngle: UnitValue = {
2728export const GradientControl = ( props : GradientControlProps ) => {
2829 const [ stops , setStops ] = useState < Array < GradientStop > > ( props . gradient . stops ) ;
2930 const [ selectedStop , setSelectedStop ] = useState < number | undefined > ( ) ;
31+ const [ isHoveredOnStop , setIsHoveredOnStop ] = useState < boolean > ( false ) ;
3032 const positions = stops
3133 . map ( ( stop ) => stop . position ?. value )
3234 . filter ( ( item ) => item !== undefined ) ;
@@ -80,24 +82,36 @@ export const GradientControl = (props: GradientControlProps) => {
8082 [ stops , selectedStop ]
8183 ) ;
8284
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 } => {
9289 const sliderWidth = event . currentTarget . offsetWidth ;
9390 const clickedPosition =
9491 event . clientX - event . currentTarget . getBoundingClientRect ( ) . left ;
9592 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 (
9795 ( position ) => Math . abs ( newPosition - position ) <= 8
9896 ) ;
9997
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 ) {
101115 return ;
102116 }
103117
@@ -131,11 +145,23 @@ export const GradientControl = (props: GradientControlProps) => {
131145 } ,
132146 ...stops . slice ( index ) ,
133147 ] ;
148+
134149 setStops ( newStops ) ;
150+ setIsHoveredOnStop ( true ) ;
151+ props . onChange ( {
152+ angle : props . gradient . angle ,
153+ stops : newStops ,
154+ sideOrCorner : props . gradient . sideOrCorner ,
155+ } ) ;
135156 } ,
136- [ stops , positions ]
157+ [ stops , positions , isStopExistsAtPosition , props ]
137158 ) ;
138159
160+ const handleMouseEnter = ( event : React . MouseEvent < HTMLSpanElement > ) => {
161+ const { isStopExistingAtPosition } = isStopExistsAtPosition ( event ) ;
162+ setIsHoveredOnStop ( isStopExistingAtPosition ) ;
163+ } ;
164+
139165 if ( isEveryStopHasAPosition === false ) {
140166 return ;
141167 }
@@ -156,15 +182,22 @@ export const GradientControl = (props: GradientControlProps) => {
156182 onValueChange = { handleValueChange }
157183 onKeyDown = { handleKeyDown }
158184 onPointerDown = { handlePointerDown }
185+ isHoveredOnStop = { isHoveredOnStop }
186+ onMouseEnter = { handleMouseEnter }
187+ onMouseMove = { handleMouseEnter }
188+ onMouseLeave = { ( ) => {
189+ setIsHoveredOnStop ( false ) ;
190+ } }
159191 >
160192 < Track >
161- < SliderRange css = { { cursor : "copy" } } />
193+ < SliderRange />
162194 </ Track >
163195 { stops . map ( ( stop , index ) => (
164196 < SliderThumb
165197 key = { index }
166198 onClick = { ( ) => {
167199 setSelectedStop ( index ) ;
200+ props . onThumbSelected ( index , stop ) ;
168201 } }
169202 style = { {
170203 background : toValue ( stop . color ) ,
@@ -211,6 +244,16 @@ const SliderRoot = styled(Root, {
211244 borderRadius : theme . borderRadius [ 3 ] ,
212245 touchAction : "none" ,
213246 userSelect : "none" ,
247+ variants : {
248+ isHoveredOnStop : {
249+ true : {
250+ cursor : "default" ,
251+ } ,
252+ false : {
253+ cursor : "copy" ,
254+ } ,
255+ } ,
256+ } ,
214257} ) ;
215258
216259const SliderRange = styled ( Range , {
0 commit comments