Skip to content

Commit f62fdca

Browse files
add interpolated color-stops when clicked in between the color stops
1 parent d5315fe commit f62fdca

File tree

1 file changed

+63
-2
lines changed

1 file changed

+63
-2
lines changed

apps/builder/app/builder/features/style-panel/sections/backgrounds/gradient-control.tsx

Lines changed: 63 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { toValue, UnitValue } from "@webstudio-is/css-engine";
1+
import { toValue, UnitValue, type RgbValue } from "@webstudio-is/css-engine";
22
import { Root, Range, Thumb, Track } from "@radix-ui/react-slider";
33
import { useState, useCallback } from "react";
44
import {
@@ -8,6 +8,10 @@ import {
88
} from "@webstudio-is/css-data";
99
import { styled, theme, Flex } from "@webstudio-is/design-system";
1010
import { ChevronBigUpIcon } from "@webstudio-is/icons";
11+
import { colord, extend } from "colord";
12+
import mixPlugin from "colord/plugins/mix";
13+
14+
extend([mixPlugin]);
1115

1216
type GradientControlProps = {
1317
gradient: ParsedGradient;
@@ -76,6 +80,62 @@ export const GradientControl = (props: GradientControlProps) => {
7680
[stops, selectedStop]
7781
);
7882

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.
92+
const sliderWidth = event.currentTarget.offsetWidth;
93+
const clickedPosition =
94+
event.clientX - event.currentTarget.getBoundingClientRect().left;
95+
const newPosition = Math.ceil((clickedPosition / sliderWidth) * 100);
96+
const isExistingPosition = positions.some(
97+
(position) => Math.abs(newPosition - position) <= 8
98+
);
99+
100+
if (isExistingPosition === true) {
101+
return;
102+
}
103+
104+
event.preventDefault();
105+
const newStopIndex = positions.findIndex(
106+
(position) => position > newPosition
107+
);
108+
109+
const index = newStopIndex === -1 ? stops.length : newStopIndex;
110+
const prevColor = stops[index === 0 ? 0 : index - 1].color;
111+
const nextColor =
112+
stops[index === positions.length ? index - 1 : index].color;
113+
114+
const interpolationColor = colord(toValue(prevColor))
115+
.mix(colord(toValue(nextColor)), newPosition / 100)
116+
.toRgb();
117+
118+
const newColorStop: RgbValue = {
119+
type: "rgb",
120+
alpha: interpolationColor.a,
121+
r: interpolationColor.r,
122+
g: interpolationColor.g,
123+
b: interpolationColor.b,
124+
};
125+
126+
const newStops: GradientStop[] = [
127+
...stops.slice(0, index),
128+
{
129+
color: newColorStop,
130+
position: { type: "unit", value: newPosition, unit: "%" },
131+
},
132+
...stops.slice(index),
133+
];
134+
setStops(newStops);
135+
},
136+
[stops, positions]
137+
);
138+
79139
if (isEveryStopHasAPosition === false) {
80140
return;
81141
}
@@ -95,6 +155,7 @@ export const GradientControl = (props: GradientControlProps) => {
95155
value={positions}
96156
onValueChange={handleValueChange}
97157
onKeyDown={handleKeyDown}
158+
onPointerDown={handlePointerDown}
98159
>
99160
<Track>
100161
<SliderRange css={{ cursor: "copy" }} />
@@ -119,7 +180,7 @@ export const GradientControl = (props: GradientControlProps) => {
119180
with a different color-stop. So, we are not allowing the user to move the hint along the slider.
120181
121182
None of the tools are even displaying the hints at the moment. We are just displaying them so users can know
122-
they are hints associated too.
183+
they are hints associated with stops if they managed to add gradient from the advanced tab.
123184
*/}
124185
{hints.map((hint) => {
125186
return (

0 commit comments

Comments
 (0)