diff --git a/apps/builder/app/builder/features/style-panel/sections/backgrounds/gradient-control.stories.tsx b/apps/builder/app/builder/features/style-panel/sections/backgrounds/gradient-control.stories.tsx
index 07a95bea6605..b84427104399 100644
--- a/apps/builder/app/builder/features/style-panel/sections/backgrounds/gradient-control.stories.tsx
+++ b/apps/builder/app/builder/features/style-panel/sections/backgrounds/gradient-control.stories.tsx
@@ -22,6 +22,7 @@ export const GradientWithoutAngle = () => {
onChange={(value) => {
setGradient(reconstructLinearGradient(value));
}}
+ onThumbSelected={() => {}}
/>
{gradient}
@@ -40,6 +41,7 @@ export const GradientWithAngleAndHints = () => {
onChange={(value) => {
setGradient(reconstructLinearGradient(value));
}}
+ onThumbSelected={() => {}}
/>
{gradient}
@@ -58,6 +60,7 @@ export const GradientWithSideOrCorner = () => {
onChange={(value) => {
setGradient(reconstructLinearGradient(value));
}}
+ onThumbSelected={() => {}}
/>
{gradient}
diff --git a/apps/builder/app/builder/features/style-panel/sections/backgrounds/gradient-control.tsx b/apps/builder/app/builder/features/style-panel/sections/backgrounds/gradient-control.tsx
index 9a9a0be39e52..3115d5821049 100644
--- a/apps/builder/app/builder/features/style-panel/sections/backgrounds/gradient-control.tsx
+++ b/apps/builder/app/builder/features/style-panel/sections/backgrounds/gradient-control.tsx
@@ -16,6 +16,7 @@ extend([mixPlugin]);
type GradientControlProps = {
gradient: ParsedGradient;
onChange: (value: ParsedGradient) => void;
+ onThumbSelected: (index: number, stop: GradientStop) => void;
};
const defaultAngle: UnitValue = {
@@ -27,6 +28,7 @@ const defaultAngle: UnitValue = {
export const GradientControl = (props: GradientControlProps) => {
const [stops, setStops] = useState>(props.gradient.stops);
const [selectedStop, setSelectedStop] = useState();
+ const [isHoveredOnStop, setIsHoveredOnStop] = useState(false);
const positions = stops
.map((stop) => stop.position?.value)
.filter((item) => item !== undefined);
@@ -80,24 +82,36 @@ export const GradientControl = (props: GradientControlProps) => {
[stops, selectedStop]
);
- const handlePointerDown = useCallback(
- (event: React.MouseEvent) => {
- if (event.target === undefined || event.target === null) {
- return;
- }
-
- // radix-slider automatically brings the closest thumb to the clicked position.
- // But, we want it be prevented. So, we can add a new color-stop where the user is cliked.
- // And handle the even for scrubing when the user is dragging the thumb.
+ const isStopExistsAtPosition = useCallback(
+ (
+ event: React.MouseEvent
+ ): { isStopExistingAtPosition: boolean; newPosition: number } => {
const sliderWidth = event.currentTarget.offsetWidth;
const clickedPosition =
event.clientX - event.currentTarget.getBoundingClientRect().left;
const newPosition = Math.ceil((clickedPosition / sliderWidth) * 100);
- const isExistingPosition = positions.some(
+ // 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.
+ const isStopExistingAtPosition = positions.some(
(position) => Math.abs(newPosition - position) <= 8
);
- if (isExistingPosition === true) {
+ return { isStopExistingAtPosition, newPosition };
+ },
+ [positions]
+ );
+
+ const handlePointerDown = useCallback(
+ (event: React.MouseEvent) => {
+ if (event.target === undefined || event.target === null) {
+ return;
+ }
+
+ // radix-slider automatically brings the closest thumb to the clicked position.
+ // But, we want it be prevented. For adding a new color-stop where the user clicked.
+ // And handle the change in values only even for scrubing when the user is dragging the thumb.
+ const { isStopExistingAtPosition, newPosition } =
+ isStopExistsAtPosition(event);
+ if (isStopExistingAtPosition === true) {
return;
}
@@ -131,11 +145,23 @@ export const GradientControl = (props: GradientControlProps) => {
},
...stops.slice(index),
];
+
setStops(newStops);
+ setIsHoveredOnStop(true);
+ props.onChange({
+ angle: props.gradient.angle,
+ stops: newStops,
+ sideOrCorner: props.gradient.sideOrCorner,
+ });
},
- [stops, positions]
+ [stops, positions, isStopExistsAtPosition, props]
);
+ const handleMouseEnter = (event: React.MouseEvent) => {
+ const { isStopExistingAtPosition } = isStopExistsAtPosition(event);
+ setIsHoveredOnStop(isStopExistingAtPosition);
+ };
+
if (isEveryStopHasAPosition === false) {
return;
}
@@ -156,15 +182,22 @@ export const GradientControl = (props: GradientControlProps) => {
onValueChange={handleValueChange}
onKeyDown={handleKeyDown}
onPointerDown={handlePointerDown}
+ isHoveredOnStop={isHoveredOnStop}
+ onMouseEnter={handleMouseEnter}
+ onMouseMove={handleMouseEnter}
+ onMouseLeave={() => {
+ setIsHoveredOnStop(false);
+ }}
>
{stops.map((stop, index) => (
{
setSelectedStop(index);
+ props.onThumbSelected(index, stop);
}}
style={{
background: toValue(stop.color),
@@ -211,6 +244,16 @@ const SliderRoot = styled(Root, {
borderRadius: theme.borderRadius[3],
touchAction: "none",
userSelect: "none",
+ variants: {
+ isHoveredOnStop: {
+ true: {
+ cursor: "default",
+ },
+ false: {
+ cursor: "copy",
+ },
+ },
+ },
});
const SliderRange = styled(Range, {