70 |
changeGradientControl({ type: 'linear' })}
73 | />
74 |
changeGradientControl({ type: 'radial' })}
77 | />
78 |
79 | {
80 | type === 'linear'
81 | && (
82 |
83 |
92 |
93 |
94 | {degree}
95 | °
96 |
97 |
98 |
99 | )
100 | }
101 |
102 | );
103 | }
104 |
105 | export default GradientControls;
106 |
--------------------------------------------------------------------------------
/src/lib/components/ColorPicker/Area/Picking/index.jsx:
--------------------------------------------------------------------------------
1 | import React, {
2 | useEffect, useCallback, useRef, useState,
3 | } from 'react';
4 |
5 | import { changePicker, getRgbByHue } from 'lib/helpers';
6 | import { useMouseEvents } from 'lib/hooks';
7 |
8 | function Picking({
9 | red,
10 | green,
11 | blue,
12 | hue,
13 | saturation,
14 | value,
15 | updateRgb,
16 | }) {
17 | const pickingAreaRef = useRef();
18 | const [width, setWidth] = useState(0);
19 | const [height, setHeight] = useState(0);
20 |
21 | useEffect(() => {
22 | if (pickingAreaRef.current) {
23 | setWidth(pickingAreaRef.current.clientWidth);
24 | setHeight(pickingAreaRef.current.clientHeight);
25 | }
26 | }, [pickingAreaRef, setWidth, setHeight]);
27 |
28 | useEffect(() => {
29 | const { red, green, blue } = getRgbByHue(hue);
30 |
31 | pickingAreaRef.current.style.backgroundColor = `rgb(${red}, ${green}, ${blue})`;
32 | }, [hue]);
33 |
34 | // generate offsetLeft by saturation
35 | const offsetLeft = ((saturation * width / 100) | 0) - 6;
36 |
37 | // generate offsetTop by value
38 | const offsetTop = (height - (value * height / 100) | 0) - 6;
39 |
40 | const getPointerStyle = {
41 | backgroundColor: `rgb(${red}, ${green}, ${blue})`,
42 | left: `${offsetLeft}px`,
43 | top: `${offsetTop}px`,
44 | };
45 |
46 | const mouseDownHandler = useCallback(event => {
47 | const elementX = event.currentTarget.getBoundingClientRect().x;
48 | const elementY = event.currentTarget.getBoundingClientRect().y;
49 | const startX = event.pageX;
50 | const startY = event.pageY;
51 | const positionX = startX - elementX;
52 | const positionY = startY - elementY;
53 |
54 | const color = changePicker(positionX, positionY, height, width, hue);
55 |
56 | updateRgb(color, 'onStartChange');
57 | return {
58 | startX,
59 | startY,
60 | positionX,
61 | positionY,
62 |
63 | };
64 | }, [height, width, hue, updateRgb]);
65 |
66 | const changeObjectPositions = useCallback((event, {
67 | startX, startY, positionX, positionY,
68 | }) => {
69 | const moveX = event.pageX - startX;
70 | const moveY = event.pageY - startY;
71 | positionX += moveX;
72 | positionY += moveY;
73 |
74 | const color = changePicker(positionX, positionY, height, width, hue);
75 |
76 | return {
77 | positions: {
78 | positionX,
79 | positionY,
80 | startX: event.pageX,
81 | startY: event.pageY,
82 | },
83 | color,
84 | };
85 | }, [height, hue, width]);
86 |
87 | const mouseMoveHandler = useCallback((event, {
88 | startX, startY, positionX, positionY,
89 | }) => {
90 | const { positions, color } = changeObjectPositions(event, {
91 | startX, startY, positionX, positionY,
92 | });
93 |
94 | updateRgb(color, 'onChange');
95 |
96 | return positions;
97 | }, [updateRgb, changeObjectPositions]);
98 |
99 | const mouseUpHandler = useCallback((event, {
100 | startX, startY, positionX, positionY,
101 | }) => {
102 | const { positions, color } = changeObjectPositions(event, {
103 | startX, startY, positionX, positionY,
104 | });
105 |
106 | updateRgb(color, 'onEndChange');
107 |
108 | return positions;
109 | }, [updateRgb, changeObjectPositions]);
110 |
111 | const mouseEvents = useMouseEvents(mouseDownHandler, mouseMoveHandler, mouseUpHandler);
112 |
113 | const onMouseDown = event => {
114 | mouseEvents(event);
115 | };
116 |
117 | return (
118 |
129 | );
130 | }
131 |
132 | export default Picking;
133 |
--------------------------------------------------------------------------------
/src/lib/components/ColorPicker/Gradient/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { useCallback, useEffect, useState } from 'react';
2 |
3 | import { useMount } from 'lib/hooks';
4 | import { rgbToHsv, getRightValue, generateGradientStyle } from 'lib/helpers';
5 |
6 | import Area from '../Area';
7 | import Preview from '../Preview';
8 | import GradientControls from './GradientControls';
9 |
10 | function Gradient({
11 | points, type, degree, onChange, onStartChange, onEndChange,
12 | }) {
13 | const [activePointIndex, setActivePointIndex] = useState(0);
14 | const [gradientPoints, setGradientPoints] = useState(points);
15 | const [activePoint, setActivePoint] = useState(gradientPoints[0]);
16 | const [colorRed, setColorRed] = useState(activePoint.red);
17 | const [colorGreen, setColorGreen] = useState(activePoint.green);
18 | const [colorBlue, setColorBlue] = useState(activePoint.blue);
19 | const [colorAlpha, setColorAlpha] = useState(activePoint.alpha);
20 | const [colorHue, setColorHue] = useState(0);
21 | const [colorSaturation, setColorSaturation] = useState(100);
22 | const [colorValue, setColorValue] = useState(100);
23 | const [gradientType, setGradientType] = useState(type);
24 | const [gradientDegree, setGradientDegree] = useState(degree);
25 |
26 | const actions = {
27 | onChange,
28 | onStartChange,
29 | onEndChange,
30 | };
31 |
32 | useMount(() => {
33 | const { hue, saturation, value } = rgbToHsv({ red: colorRed, green: colorGreen, blue: colorBlue });
34 |
35 | setColorHue(hue);
36 | setColorSaturation(saturation);
37 | setColorValue(value);
38 | });
39 |
40 | const removePoint = useCallback((index = activePointIndex) => {
41 | if (gradientPoints.length <= 2) {
42 | return;
43 | }
44 |
45 | const localGradientPoints = gradientPoints.slice();
46 | localGradientPoints.splice(index, 1);
47 |
48 | setGradientPoints(localGradientPoints);
49 |
50 | if (index > 0) {
51 | setActivePointIndex(index - 1);
52 | }
53 |
54 | onChange && onChange({
55 | points: localGradientPoints,
56 | type: gradientType,
57 | degree: gradientDegree,
58 | style: generateGradientStyle(localGradientPoints, gradientType, gradientDegree),
59 | });
60 | }, [gradientPoints, activePointIndex, gradientType, gradientDegree, onChange]);
61 |
62 | const keyUpHandler = useCallback(event => {
63 | if ((event.keyCode === 46 || event.keyCode === 8)) {
64 | removePoint(activePointIndex);
65 | }
66 | }, [activePointIndex, removePoint]);
67 |
68 | useEffect(() => {
69 | document.addEventListener('keyup', keyUpHandler);
70 |
71 | return () => {
72 | document.removeEventListener('keyup', keyUpHandler);
73 | };
74 | });
75 |
76 | const changeGradientControl = useCallback(({ type, degree }, actionName = 'onChange') => {
77 | type = getRightValue(type, gradientType);
78 | degree = getRightValue(degree, gradientDegree);
79 |
80 | setGradientType(type);
81 | setGradientDegree(degree);
82 |
83 | const action = actions[actionName];
84 |
85 | action && action({
86 | points: gradientPoints,
87 | type,
88 | degree,
89 | style: generateGradientStyle(gradientPoints, type, degree),
90 | });
91 | }, [actions, gradientPoints, gradientDegree, gradientType]);
92 |
93 | const changeActivePointIndex = useCallback(index => {
94 | setActivePointIndex(index);
95 |
96 | const localGradientPoint = gradientPoints[index];
97 | const {
98 | red, green, blue, alpha,
99 | } = localGradientPoint;
100 | setActivePoint(localGradientPoint);
101 |
102 | setColorRed(red);
103 | setColorGreen(green);
104 | setColorBlue(blue);
105 | setColorAlpha(alpha);
106 |
107 | const { hue, saturation, value } = rgbToHsv({ red, green, blue });
108 |
109 | setColorHue(hue);
110 | setColorSaturation(saturation);
111 | setColorValue(value);
112 | }, [gradientPoints]);
113 |
114 | const updateColor = useCallback(({
115 | red, green, blue, alpha, hue, saturation, value,
116 | }, actionName = 'onChange') => {
117 | red = getRightValue(red, colorRed);
118 | green = getRightValue(green, colorGreen);
119 | blue = getRightValue(blue, colorBlue);
120 | alpha = getRightValue(alpha, colorAlpha);
121 | hue = getRightValue(hue, colorHue);
122 | saturation = getRightValue(saturation, colorSaturation);
123 | value = getRightValue(value, colorValue);
124 |
125 | const localGradientPoints = gradientPoints.slice();
126 |
127 | localGradientPoints[activePointIndex] = {
128 | ...localGradientPoints[activePointIndex],
129 | red,
130 | green,
131 | blue,
132 | alpha,
133 | };
134 |
135 | setColorRed(red);
136 | setColorGreen(green);
137 | setColorBlue(blue);
138 | setColorAlpha(alpha);
139 | setColorHue(hue);
140 | setColorSaturation(saturation);
141 | setColorValue(value);
142 | setGradientPoints(localGradientPoints);
143 |
144 | const action = actions[actionName];
145 |
146 | action && action({
147 | points: localGradientPoints,
148 | type: gradientType,
149 | degree: gradientDegree,
150 | style: generateGradientStyle(localGradientPoints, gradientType, gradientDegree),
151 | });
152 | }, [
153 | colorRed, colorGreen, colorBlue, colorAlpha,
154 | colorHue, colorSaturation, colorValue,
155 | activePointIndex, gradientPoints, actions, gradientType, gradientDegree,
156 | ]);
157 |
158 | const updateGradientLeft = useCallback((left, index, actionName = 'onChange') => {
159 | const localGradientPoints = gradientPoints.slice();
160 | localGradientPoints[index].left = left;
161 |
162 | setGradientPoints(localGradientPoints);
163 |
164 | const action = actions[actionName];
165 |
166 | action && action({
167 | points: localGradientPoints,
168 | type: gradientType,
169 | degree: gradientDegree,
170 | style: generateGradientStyle(localGradientPoints, gradientType, gradientDegree),
171 | });
172 | }, [actions, gradientPoints, gradientDegree, gradientType]);
173 |
174 | const addPoint = useCallback(left => {
175 | const localGradientPoints = gradientPoints.slice();
176 |
177 | localGradientPoints.push({
178 | ...localGradientPoints[activePointIndex],
179 | left,
180 | });
181 |
182 | setGradientPoints(localGradientPoints);
183 | setActivePointIndex(localGradientPoints.length - 1);
184 |
185 | onChange && onChange({
186 | points: localGradientPoints,
187 | type: gradientType,
188 | degree: gradientDegree,
189 | style: generateGradientStyle(localGradientPoints, gradientType, gradientDegree),
190 | });
191 | }, [onChange, gradientPoints, activePointIndex, gradientType, gradientDegree]);
192 |
193 | return (
194 | <>
195 |
200 |
220 |
227 | >
228 | );
229 | }
230 |
231 | export default Gradient;
232 |
--------------------------------------------------------------------------------
/src/lib/index.scss:
--------------------------------------------------------------------------------
1 | * {
2 | box-sizing: border-box;
3 | }
4 |
5 | .ui-color-picker {
6 | margin: 8px;
7 | background-color: #FFFFFF;
8 | display: flex;
9 | flex-direction: column;
10 | width: 280px;
11 | user-select: none;
12 |
13 | .gradient-controls {
14 | display: flex;
15 | flex-direction: row;
16 | align-items: center;
17 | width: 100%;
18 | margin-bottom: 8px;
19 | margin-top: 5px;
20 | height: 24px;
21 | padding: 0 16px;
22 |
23 | .gradient-type {
24 | flex: 1;
25 | display: flex;
26 |
27 | .gradient-type-item {
28 | height: 28px;
29 | width: 28px;
30 | border-radius: 50%;
31 | position: relative;
32 | cursor: pointer;
33 |
34 | &.active {
35 | &::after {
36 | content: '';
37 | display: block;
38 | position: absolute;
39 | top: -3px;
40 | bottom: -3px;
41 | left: -3px;
42 | right: -3px;
43 | border: 2px solid #1F2667;
44 | border-radius: 50%;
45 | }
46 | }
47 |
48 | &.liner-gradient {
49 | background: linear-gradient(270deg, #FFFFFF 0%, #1F2667 100%);
50 | }
51 |
52 | &.radial-gradient {
53 | margin-left: 8px;
54 | background: radial-gradient(circle, #FFFFFF 0%, #1F2667 100%);
55 | }
56 | }
57 |
58 | }
59 |
60 | .gradient-degrees-options {
61 | position: relative;
62 |
63 | .gradient-degrees {
64 | display: -ms-flexbox;
65 | display: flex;
66 | -webkit-box-pack: center;
67 | -ms-flex-pack: center;
68 | justify-content: center;
69 | -webkit-box-align: center;
70 | -ms-flex-align: center;
71 | align-items: center;
72 | position: relative;
73 | width: 28px;
74 | height: 28px;
75 | border: 3px solid #1F2667;
76 | border-radius: 18px;
77 | margin-right: 54px;
78 |
79 | .gradient-degree-center {
80 | position: relative;
81 | width: 6px;
82 | height: 22px;
83 | pointer-events: none;
84 |
85 | .gradient-degree-pointer {
86 | position: absolute;
87 | width: 6px;
88 | height: 6px;
89 | top: 2px;
90 | border-radius: 3px;
91 | background: #1F2667;
92 | }
93 | }
94 | }
95 |
96 | .gradient-degree-value {
97 | position: absolute;
98 | top: 0;
99 | right: 0;
100 | width: 48px;
101 | height: 28px;
102 | display: flex;
103 | align-items: center;
104 | justify-content: center;
105 | border: 1px solid #bbbfc5;
106 | border-radius: 6px;
107 |
108 | p {
109 | text-align: center;
110 | padding: 0 6px;
111 | }
112 | }
113 | }
114 | }
115 |
116 | .picker-area {
117 | display: flex;
118 | flex-direction: column;
119 | padding: 0 16px;
120 |
121 | &.gradient-tab {
122 | .picking-area {
123 | margin-bottom: 10px;
124 | }
125 | }
126 |
127 | .picking-area {
128 | width: 100%;
129 | height: 160px;
130 | margin-bottom: 16px;
131 | position: relative;
132 | border-radius: 8px;
133 |
134 | &:hover {
135 | cursor: default;
136 | }
137 |
138 | .picking-area-overlay1 {
139 | height: 100%;
140 | width: 100%;
141 | background: linear-gradient(to right, white 0%, rgba(255, 255, 255, 0) 100%);
142 | border-radius: 3px;
143 |
144 | .picking-area-overlay2 {
145 | height: 100%;
146 | width: 100%;
147 | background: linear-gradient(to bottom, rgba(0, 0, 0, 0) 0%, black 100%);
148 | border-radius: 8px;
149 | }
150 | }
151 | }
152 |
153 | .preview {
154 | display: flex;
155 | flex-direction: row;
156 | margin-bottom: 16px;
157 |
158 | .preview-box {
159 | box-sizing: border-box;
160 | height: 36px;
161 | width: 36px;
162 | border-radius: 8px;
163 | border: 1px solid #EBEDF5;
164 | }
165 |
166 | .color-hue-alpha {
167 | display: flex;
168 | flex-direction: column;
169 | flex: 1;
170 | margin-left: 6px;
171 |
172 | .hue {
173 | width: 100%;
174 | position: relative;
175 | border-radius: 10px;
176 | margin-bottom: 8px;
177 | padding: 0 7px;
178 | background-color: red;
179 |
180 | .hue-area {
181 | position: relative;
182 | height: 14px;
183 | background: -webkit-linear-gradient(left, #ff0000, #ff0080, #ff00ff, #8000ff, #0000ff, #0080ff, #00ffff, #00ff80, #00ff00, #80ff00, #ffff00, #ff8000, #ff0000);
184 | background: -o-linear-gradient(left, #ff0000, #ff8000, #ffff00, #80ff00, #00ff00, #00ff80, #00ffff, #0080ff, #0000ff, #8000ff, #ff00ff, #ff0080, #ff0000);
185 | background: -ms-linear-gradient(left, #ff0000, #ff8000, #ffff00, #80ff00, #00ff00, #00ff80, #00ffff, #0080ff, #0000ff, #8000ff, #ff00ff, #ff0080, #ff0000);
186 | background: -moz-linear-gradient(left, #ff0000, #ff8000, #ffff00, #80ff00, #00ff00, #00ff80, #00ffff, #0080ff, #0000ff, #8000ff, #ff00ff, #ff0080, #ff0000);
187 | background: linear-gradient(to right, #ff0000, #ff8000, #ffff00, #80ff00, #00ff00, #00ff80, #00ffff, #0080ff, #0000ff, #8000ff, #ff00ff, #ff0080, #ff0000);
188 | }
189 | }
190 |
191 | .alpha {
192 | position: relative;
193 | width: 100%;
194 | overflow: hidden;
195 | border-radius: 10px;
196 | height: 14px;
197 | cursor: pointer;
198 |
199 | .gradient {
200 | position: absolute;
201 | top: 0;
202 | left: 0;
203 | right: 0;
204 | bottom: 0;
205 | }
206 |
207 | .alpha-area {
208 | width: 100%;
209 | height: 100%;
210 | background: url("assets/images/alpha-background.svg");
211 | background-size: auto;
212 | background-position: 50% 50%;
213 | border-radius: 10px;
214 | padding: 0 7px;
215 |
216 | .alpha-mask {
217 | width: 100%;
218 | height: 100%;
219 | position: relative;
220 | }
221 | }
222 | }
223 | }
224 | }
225 |
226 | .gradient {
227 | width: 100%;
228 | height: 14px;
229 | position: relative;
230 | cursor: pointer;
231 | border-radius: 10px;
232 | margin-bottom: 8px;
233 | padding: 0 7px;
234 |
235 | .gradient-slider-container {
236 | height: 100%;
237 | width: 100%;
238 | position: relative;
239 | }
240 | }
241 |
242 | .picker-pointer {
243 | position: absolute;
244 | top: 1px;
245 | height: 12px;
246 | width: 12px;
247 | border: 1px solid #FFFFFF;
248 | background: transparent;
249 | border-radius: 50%;
250 | box-shadow: 0 0 4px 0 rgba(0, 0, 0, 0.3);
251 |
252 | .child-point {
253 | position: absolute;
254 | top: 50%;
255 | left: 50%;
256 | transform: translate(-50%, -50%);
257 | height: 3px;
258 | width: 3px;
259 | background: #FFFFFF;
260 | border-radius: 50%;
261 | opacity: 0;
262 | transition: opacity 0.33s;
263 |
264 | &.active {
265 | opacity: 1;
266 | }
267 | }
268 | }
269 | }
270 |
271 | .color-preview-area {
272 | margin-bottom: 8px;
273 | padding: 0 16px;
274 |
275 | .input-group {
276 | width: 100%;
277 | display: flex;
278 | flex-direction: row;
279 | justify-content: space-between;
280 |
281 | .uc-field-group:not(:last-child) {
282 | margin-right: 7px;
283 | }
284 | }
285 |
286 | .hex {
287 | width: 65px;
288 | }
289 |
290 | .rgb {
291 | width: 40px;
292 | }
293 | }
294 |
295 | .colors {
296 | padding: 3px 16px 0;
297 |
298 | .colors-label {
299 | display: flex;
300 | align-items: center;
301 | margin-bottom: 4px;
302 | cursor: pointer;
303 |
304 | .uc-icon {
305 | margin-right: 8px;
306 | transition: transform 0.3s;
307 | }
308 |
309 | .tp-text {
310 | text-transform: uppercase;
311 | }
312 |
313 | &.show {
314 | & + .colors-item-container {
315 | max-height: 80px;
316 | padding-bottom: 16px;
317 | }
318 |
319 | .uc-icon {
320 | transform: rotate(-90deg);
321 | }
322 | }
323 | }
324 |
325 | .template {
326 | display: flex;
327 | flex-direction: column;
328 | }
329 |
330 | .global {
331 | display: flex;
332 | flex-direction: column;
333 | align-items: flex-start;
334 | }
335 |
336 | .colors-item-container {
337 | display: flex;
338 | flex-wrap: wrap;
339 | width: 100%;
340 | transition: max-height 0.3s, padding-bottom 0.3s;
341 | max-height: 0;
342 | overflow: hidden;
343 |
344 | .colors-item {
345 | height: 24px;
346 | width: 24px;
347 | border-radius: 50%;
348 | background-color: #EBEDF5;
349 | cursor: pointer;
350 | position: relative;
351 | margin-top: 4px;
352 | flex-shrink: 0;
353 |
354 | &:not(.plus) {
355 | border: 1px solid #EBEDF5;
356 | }
357 |
358 | &.empty {
359 | display: flex;
360 | align-items: center;
361 | justify-content: center;
362 |
363 | .line {
364 | width: 2px;
365 | height: 16px;
366 | background-color: #8892B3;
367 | border-radius: 1px;
368 | transform: rotate(45deg);
369 | }
370 | }
371 |
372 | &.plus {
373 | margin-bottom: 4px;
374 |
375 | .uc-icon {
376 | position: absolute;
377 | top: 50%;
378 | left: 50%;
379 | transform: translate(-50%, -50%);
380 | opacity: 1;
381 | }
382 | }
383 |
384 | &:not(:first-child):not(:nth-child(9)) {
385 | margin-left: 8px;
386 | }
387 |
388 | .uc-icon {
389 | position: absolute;
390 | right: -8px;
391 | top: -3px;
392 | opacity: 0;
393 | transition: opacity 0.3s;
394 | }
395 |
396 | &:hover {
397 | .uc-icon {
398 | opacity: 1;
399 | }
400 | }
401 |
402 | &.active {
403 | &::after {
404 | content: '';
405 | display: block;
406 | position: absolute;
407 | top: -3px;
408 | bottom: -3px;
409 | left: -3px;
410 | right: -3px;
411 | border: 2px solid #8892B3;
412 | border-radius: 50%;
413 | }
414 | }
415 | }
416 | }
417 | }
418 | }
419 |
--------------------------------------------------------------------------------