reanimated-color-picker 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ColorPicker.js +724 -0
- package/ColorsConversionFormulas.js +220 -0
- package/LICENSE +21 -0
- package/NamedColors.js +147 -0
- package/assets/Background.png +0 -0
- package/assets/Hue.png +0 -0
- package/assets/Opacity.png +0 -0
- package/index.d.ts +121 -0
- package/index.js +4 -0
- package/package.json +28 -0
package/ColorPicker.js
ADDED
|
@@ -0,0 +1,724 @@
|
|
|
1
|
+
import React, { useRef, useEffect, useMemo } from 'react';
|
|
2
|
+
import { View, Image, I18nManager, Pressable, TextInput, Text, StyleSheet } from 'react-native';
|
|
3
|
+
import { PanGestureHandler, GestureHandlerRootView } from 'react-native-gesture-handler';
|
|
4
|
+
import Animated, {
|
|
5
|
+
runOnJS,
|
|
6
|
+
useAnimatedGestureHandler,
|
|
7
|
+
useAnimatedStyle,
|
|
8
|
+
useSharedValue,
|
|
9
|
+
useAnimatedProps,
|
|
10
|
+
withTiming,
|
|
11
|
+
} from 'react-native-reanimated';
|
|
12
|
+
import { ALPHA_HEX, COLOR_HSVA, CONTRAST_RATIO, HSL_HEX, HSL_RGB, HSV_HSL } from './ColorsConversionFormulas';
|
|
13
|
+
|
|
14
|
+
const PANEL_IMAGE = require('./assets/Background.png'),
|
|
15
|
+
OPACITY_IMAGE = require('./assets/Opacity.png'),
|
|
16
|
+
HUE_IMAGE = require('./assets/Hue.png');
|
|
17
|
+
|
|
18
|
+
const isRtl = I18nManager.isRTL,
|
|
19
|
+
HUE_MAX = 360,
|
|
20
|
+
SB_MAX = 100,
|
|
21
|
+
OPACITY_MAX = 100,
|
|
22
|
+
CONTRAST_RATIO_MIN = 4.5,
|
|
23
|
+
SWATCHE_SIZE = 30,
|
|
24
|
+
TRACKS_HEIGHT = 25,
|
|
25
|
+
HANDLES_SCALE = 1.2,
|
|
26
|
+
WIDTH = 300;
|
|
27
|
+
|
|
28
|
+
const SWATCHES_COLORS = [
|
|
29
|
+
'#f44336',
|
|
30
|
+
'#E91E63',
|
|
31
|
+
'#9C27B0',
|
|
32
|
+
'#673AB7',
|
|
33
|
+
'#3F51B5',
|
|
34
|
+
'#2196F3',
|
|
35
|
+
'#03A9F4',
|
|
36
|
+
'#00BCD4',
|
|
37
|
+
'#009688',
|
|
38
|
+
'#4CAF50',
|
|
39
|
+
'#8BC34A',
|
|
40
|
+
'#CDDC39',
|
|
41
|
+
'#FFEB3B',
|
|
42
|
+
'#FFC107',
|
|
43
|
+
'#FF9800',
|
|
44
|
+
'#FF5722',
|
|
45
|
+
'#795548',
|
|
46
|
+
'#9E9E9E',
|
|
47
|
+
'#607D8B',
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
Animated.addWhitelistedNativeProps({ text: true });
|
|
51
|
+
const AnimatedTextInput = Animated.createAnimatedComponent(TextInput);
|
|
52
|
+
|
|
53
|
+
const ReText = props => {
|
|
54
|
+
const { text, style, customStyle } = { ...props };
|
|
55
|
+
|
|
56
|
+
const animatedProps = useAnimatedProps(() => ({ text: text.value }));
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<AnimatedTextInput
|
|
60
|
+
underlineColorAndroid='transparent'
|
|
61
|
+
editable={false}
|
|
62
|
+
value={text.value}
|
|
63
|
+
style={[{ fontWeight: 'bold', padding: 0, height: '100%', width: '100%', textAlign: 'center' }, customStyle, style]}
|
|
64
|
+
{...{ animatedProps }}
|
|
65
|
+
/>
|
|
66
|
+
);
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export default function ColorPicker({
|
|
70
|
+
tracksHeight = TRACKS_HEIGHT,
|
|
71
|
+
thumbsSize = TRACKS_HEIGHT * 1.4,
|
|
72
|
+
value = '#418181',
|
|
73
|
+
onChange,
|
|
74
|
+
onComplete,
|
|
75
|
+
width = WIDTH,
|
|
76
|
+
style = {},
|
|
77
|
+
children = <Text>NO CHILDREN</Text>,
|
|
78
|
+
} = {}) {
|
|
79
|
+
const isFirstRender = useRef(true);
|
|
80
|
+
const initialColor = useRef(COLOR_HSVA(value));
|
|
81
|
+
const previewColorFormat = useRef('hex');
|
|
82
|
+
|
|
83
|
+
const panelThumbeSize = useRef(thumbsSize);
|
|
84
|
+
const hueThumbeSize = useRef(thumbsSize);
|
|
85
|
+
const opacityThumbeSize = useRef(thumbsSize);
|
|
86
|
+
|
|
87
|
+
const hue = useRef(initialColor.current.h);
|
|
88
|
+
const saturation = useRef(initialColor.current.s);
|
|
89
|
+
const brightness = useRef(initialColor.current.b);
|
|
90
|
+
const alpha = useRef(initialColor.current.a);
|
|
91
|
+
|
|
92
|
+
const color_hsl = color => {
|
|
93
|
+
color ??= { h: hue.current, s: saturation.current, b: brightness.current };
|
|
94
|
+
const { h, s, l } = HSV_HSL(color.h, color.s, color.b);
|
|
95
|
+
return `hsl(${h}, ${s}%, ${l}%)`;
|
|
96
|
+
};
|
|
97
|
+
const color_hsla = color => {
|
|
98
|
+
color ??= { h: hue.current, s: saturation.current, b: brightness.current, a: alpha.current };
|
|
99
|
+
const { h, s, l } = HSV_HSL(color.h, color.s, color.b);
|
|
100
|
+
return `hsla(${h}, ${s}%, ${l}%, ${color.a / 100})`;
|
|
101
|
+
};
|
|
102
|
+
const color_hex = color => {
|
|
103
|
+
color ??= { h: hue.current, s: saturation.current, b: brightness.current, a: alpha.current };
|
|
104
|
+
const { h, s, l } = HSV_HSL(color.h, color.s, color.b);
|
|
105
|
+
return HSL_HEX(h, s, l) + (color.a === 100 ? '' : ALPHA_HEX(color.a));
|
|
106
|
+
};
|
|
107
|
+
const color_rgb = color => {
|
|
108
|
+
color ??= { h: hue.current, s: saturation.current, b: brightness.current };
|
|
109
|
+
const { h, s, l } = HSV_HSL(color.h, color.s, color.b);
|
|
110
|
+
const { r, g, b } = HSL_RGB(h, s, l);
|
|
111
|
+
return `rgb(${r}, ${g}, ${b})`;
|
|
112
|
+
};
|
|
113
|
+
const color_rgba = color => {
|
|
114
|
+
color ??= { h: hue.current, s: saturation.current, b: brightness.current, a: alpha.current };
|
|
115
|
+
const { h, s, l } = HSV_HSL(color.h, color.s, color.b);
|
|
116
|
+
const { r, g, b } = HSL_RGB(h, s, l);
|
|
117
|
+
return `rgba(${r}, ${g}, ${b}, ${color.a / 100})`;
|
|
118
|
+
};
|
|
119
|
+
const color_hsv = color => {
|
|
120
|
+
color ??= { h: hue.current, s: saturation.current, b: brightness.current };
|
|
121
|
+
return `hsv(${color.h}, ${color.s}%, ${color.b}%)`;
|
|
122
|
+
};
|
|
123
|
+
const color_hsva = color => {
|
|
124
|
+
color ??= { h: hue.current, s: saturation.current, b: brightness.current, a: alpha.current };
|
|
125
|
+
return `hsva(${color.h}, ${color.s}%, ${color.b}%, ${color.a / 100})`;
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
const returnedResults = color => {
|
|
129
|
+
color ??= { h: hue.current, s: saturation.current, b: brightness.current, a: alpha.current };
|
|
130
|
+
return {
|
|
131
|
+
hex: color_hex(color),
|
|
132
|
+
rgb: color_rgb(color),
|
|
133
|
+
rgba: color_rgba(color),
|
|
134
|
+
hsl: color_hsl(color),
|
|
135
|
+
hsla: color_hsla(color),
|
|
136
|
+
hsv: color_hsv(color),
|
|
137
|
+
hsva: color_hsva(color),
|
|
138
|
+
};
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
const previewColor = useSharedValue(color_hex());
|
|
142
|
+
const previewColorStyle = useAnimatedStyle(() => ({ backgroundColor: previewColor.value }));
|
|
143
|
+
const previewColorWithoutOpacity = useAnimatedStyle(() => ({
|
|
144
|
+
backgroundColor: previewColor.value.length > 7 ? previewColor.value.substring(0, 7) : previewColor.value,
|
|
145
|
+
}));
|
|
146
|
+
const activeHue = useSharedValue(HSL_HEX(hue.current, 100, 50));
|
|
147
|
+
const activeHueStyle = useAnimatedStyle(() => ({ backgroundColor: activeHue.value }));
|
|
148
|
+
|
|
149
|
+
const hue_handlePos = useSharedValue(0); // for hue
|
|
150
|
+
const saturation_handlePos = useSharedValue(0); // for saturation
|
|
151
|
+
const brightness_handlePos = useSharedValue(0); // for brightness
|
|
152
|
+
const opacity_handlePos = useSharedValue(0); // for opacity
|
|
153
|
+
const previewTextColor = useSharedValue('#ffffff'); // for result text color
|
|
154
|
+
const previewText = useSharedValue(returnedResults()[previewColorFormat.current]); // for result text
|
|
155
|
+
const scale_panelHandle = useSharedValue(1); // for handles scale.
|
|
156
|
+
const scale_hueHandle = useSharedValue(1); // for handles scale.
|
|
157
|
+
const scale_opacityHandle = useSharedValue(1); // for handles scale.
|
|
158
|
+
|
|
159
|
+
const resultTextColorST = useAnimatedStyle(() => ({ color: previewTextColor.value }));
|
|
160
|
+
|
|
161
|
+
const panel_handleStyle = useAnimatedStyle(() => ({
|
|
162
|
+
backgroundColor: previewTextColor.value === '#ffffff' ? '#ffffff50' : '#00000050',
|
|
163
|
+
borderColor: previewTextColor.value,
|
|
164
|
+
transform: [
|
|
165
|
+
{ translateX: saturation_handlePos.value },
|
|
166
|
+
{ translateY: brightness_handlePos.value },
|
|
167
|
+
{ scale: scale_panelHandle.value },
|
|
168
|
+
],
|
|
169
|
+
}));
|
|
170
|
+
|
|
171
|
+
const onGestureEventFinish = () => {
|
|
172
|
+
onComplete?.(returnedResults());
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
const updateSB = (saturationChannel, brightnessChannel) => {
|
|
176
|
+
saturation.current = saturationChannel;
|
|
177
|
+
brightness.current = brightnessChannel;
|
|
178
|
+
previewColor.value = color_hex(); // to update result color.
|
|
179
|
+
previewText.value = returnedResults()[previewColorFormat.current]; // update result text
|
|
180
|
+
// change result text color based on lightness
|
|
181
|
+
const contrast = CONTRAST_RATIO(hue.current, saturation.current, brightness.current, previewTextColor.value);
|
|
182
|
+
previewTextColor.value =
|
|
183
|
+
contrast < CONTRAST_RATIO_MIN ? (previewTextColor.value === '#ffffff' ? '#000000' : '#ffffff') : previewTextColor.value;
|
|
184
|
+
|
|
185
|
+
onChange?.(returnedResults());
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
const updateHue = hueChannel => {
|
|
189
|
+
hue.current = hueChannel;
|
|
190
|
+
previewColor.value = color_hex(); // to update result color.
|
|
191
|
+
previewText.value = returnedResults()[previewColorFormat.current]; // update color text
|
|
192
|
+
// change result text color based on lightness
|
|
193
|
+
const contrast = CONTRAST_RATIO(hue.current, saturation.current, brightness.current, previewTextColor.value);
|
|
194
|
+
previewTextColor.value =
|
|
195
|
+
contrast < CONTRAST_RATIO_MIN ? (previewTextColor.value === '#ffffff' ? '#000000' : '#ffffff') : previewTextColor.value;
|
|
196
|
+
|
|
197
|
+
activeHue.value = HSL_HEX(hueChannel, 100, 50); // to update SV background color.
|
|
198
|
+
onChange?.(returnedResults());
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
const updateOpacity = alphaChannel => {
|
|
202
|
+
alpha.current = alphaChannel;
|
|
203
|
+
previewColor.value = color_hex(); // to update result color.
|
|
204
|
+
previewText.value = returnedResults()[previewColorFormat.current]; // update result text
|
|
205
|
+
|
|
206
|
+
onChange?.(returnedResults());
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
const setHandlesPos = () => {
|
|
210
|
+
const duration = 100;
|
|
211
|
+
const { h, s, b, a } = initialColor.current;
|
|
212
|
+
|
|
213
|
+
hue.current = h;
|
|
214
|
+
saturation.current = s;
|
|
215
|
+
brightness.current = b;
|
|
216
|
+
alpha.current = a;
|
|
217
|
+
// for colors
|
|
218
|
+
previewColor.value = color_hex(); // update result color.
|
|
219
|
+
previewText.value = returnedResults()[previewColorFormat.current]; // update result text
|
|
220
|
+
activeHue.value = HSL_HEX(h, 100, 50);
|
|
221
|
+
// change result text color based on lightness
|
|
222
|
+
const contrast = CONTRAST_RATIO(hue.current, saturation.current, brightness.current, previewTextColor.value);
|
|
223
|
+
previewTextColor.value =
|
|
224
|
+
contrast < CONTRAST_RATIO_MIN ? (previewTextColor.value === '#ffffff' ? '#000000' : '#ffffff') : previewTextColor.value;
|
|
225
|
+
// saturation
|
|
226
|
+
saturation_handlePos.value = withTiming(
|
|
227
|
+
isRtl ? (s / SB_MAX) * width - width + panelThumbeSize.current / 2 : (s / SB_MAX) * width - panelThumbeSize.current / 2,
|
|
228
|
+
duration
|
|
229
|
+
);
|
|
230
|
+
// brightness
|
|
231
|
+
brightness_handlePos.value = withTiming(width - (b / SB_MAX) * width - panelThumbeSize.current / 2, duration);
|
|
232
|
+
// hue
|
|
233
|
+
hue_handlePos.value = withTiming(
|
|
234
|
+
isRtl
|
|
235
|
+
? width - (h / HUE_MAX) * width - width + hueThumbeSize.current / 2
|
|
236
|
+
: width - (h / HUE_MAX) * width - hueThumbeSize.current / 2,
|
|
237
|
+
100
|
|
238
|
+
);
|
|
239
|
+
// opacity
|
|
240
|
+
opacity_handlePos.value = withTiming(
|
|
241
|
+
isRtl
|
|
242
|
+
? (a / OPACITY_MAX) * width - width + opacityThumbeSize.current / 2
|
|
243
|
+
: (a / OPACITY_MAX) * width - opacityThumbeSize.current / 2,
|
|
244
|
+
duration
|
|
245
|
+
);
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
// when value change, update handles pos.
|
|
249
|
+
useEffect(() => {
|
|
250
|
+
if (isFirstRender.current) {
|
|
251
|
+
isFirstRender.current = false;
|
|
252
|
+
setHandlesPos();
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
initialColor.current = COLOR_HSVA(value);
|
|
256
|
+
setHandlesPos();
|
|
257
|
+
}, [value, width]);
|
|
258
|
+
|
|
259
|
+
const RenderChildren = () =>
|
|
260
|
+
React.Children.map(children, child => {
|
|
261
|
+
switch (child.type.name) {
|
|
262
|
+
case 'Panel':
|
|
263
|
+
return React.cloneElement(child, {
|
|
264
|
+
width,
|
|
265
|
+
activeHueStyle,
|
|
266
|
+
panel_handleStyle,
|
|
267
|
+
previewColorWithoutOpacity,
|
|
268
|
+
scale_panelHandle,
|
|
269
|
+
saturation_handlePos,
|
|
270
|
+
brightness_handlePos,
|
|
271
|
+
updateSB,
|
|
272
|
+
onGestureEventFinish,
|
|
273
|
+
setHandlesPos,
|
|
274
|
+
panelThumbeSize,
|
|
275
|
+
thumbsSize,
|
|
276
|
+
});
|
|
277
|
+
case 'Preview':
|
|
278
|
+
return React.cloneElement(child, {
|
|
279
|
+
width,
|
|
280
|
+
initialColor,
|
|
281
|
+
returnedResults,
|
|
282
|
+
value,
|
|
283
|
+
previewColorStyle,
|
|
284
|
+
previewText,
|
|
285
|
+
resultTextColorST,
|
|
286
|
+
previewColorFormat,
|
|
287
|
+
});
|
|
288
|
+
case 'HueSlider':
|
|
289
|
+
return React.cloneElement(child, {
|
|
290
|
+
width,
|
|
291
|
+
hueThumbeSize,
|
|
292
|
+
setHandlesPos,
|
|
293
|
+
scale_hueHandle,
|
|
294
|
+
updateHue,
|
|
295
|
+
onGestureEventFinish,
|
|
296
|
+
hue_handlePos,
|
|
297
|
+
previewColorWithoutOpacity,
|
|
298
|
+
tracksHeight,
|
|
299
|
+
thumbsSize,
|
|
300
|
+
});
|
|
301
|
+
case 'OpacitySlider':
|
|
302
|
+
return React.cloneElement(child, {
|
|
303
|
+
width,
|
|
304
|
+
opacityThumbeSize,
|
|
305
|
+
setHandlesPos,
|
|
306
|
+
scale_opacityHandle,
|
|
307
|
+
activeHueStyle,
|
|
308
|
+
opacity_handlePos,
|
|
309
|
+
updateOpacity,
|
|
310
|
+
onGestureEventFinish,
|
|
311
|
+
previewColorWithoutOpacity,
|
|
312
|
+
tracksHeight,
|
|
313
|
+
thumbsSize,
|
|
314
|
+
});
|
|
315
|
+
case 'Swatches':
|
|
316
|
+
return React.cloneElement(child, {
|
|
317
|
+
width,
|
|
318
|
+
initialColor,
|
|
319
|
+
setHandlesPos,
|
|
320
|
+
onChange,
|
|
321
|
+
onComplete,
|
|
322
|
+
returnedResults,
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
default:
|
|
326
|
+
return child;
|
|
327
|
+
}
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
return (
|
|
331
|
+
<GestureHandlerRootView style={[styles.wrapper, style]}>
|
|
332
|
+
<RenderChildren />
|
|
333
|
+
</GestureHandlerRootView>
|
|
334
|
+
);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
export function Panel({
|
|
338
|
+
activeHueStyle,
|
|
339
|
+
panel_handleStyle,
|
|
340
|
+
previewColorWithoutOpacity,
|
|
341
|
+
scale_panelHandle,
|
|
342
|
+
saturation_handlePos,
|
|
343
|
+
brightness_handlePos,
|
|
344
|
+
updateSB,
|
|
345
|
+
onGestureEventFinish,
|
|
346
|
+
setHandlesPos,
|
|
347
|
+
panelThumbeSize,
|
|
348
|
+
width,
|
|
349
|
+
thumbsSize,
|
|
350
|
+
borderRadius = 5, // by user
|
|
351
|
+
thumbSize = thumbsSize, // by user
|
|
352
|
+
}) {
|
|
353
|
+
panelThumbeSize.current = thumbSize;
|
|
354
|
+
|
|
355
|
+
useEffect(() => {
|
|
356
|
+
setHandlesPos();
|
|
357
|
+
}, []);
|
|
358
|
+
|
|
359
|
+
const panel_GestureEvent = useAnimatedGestureHandler({
|
|
360
|
+
onStart: (event, ctx) => {
|
|
361
|
+
ctx.x = event.x;
|
|
362
|
+
ctx.y = event.y;
|
|
363
|
+
scale_panelHandle.value = withTiming(HANDLES_SCALE, { duration: 100 });
|
|
364
|
+
},
|
|
365
|
+
onActive: (event, ctx) => {
|
|
366
|
+
const clamp = (v, max) => Math.min(Math.max(v, 0), max);
|
|
367
|
+
|
|
368
|
+
const x = event.translationX;
|
|
369
|
+
const y = event.translationY;
|
|
370
|
+
const posX = clamp(x + ctx.x, width);
|
|
371
|
+
const posY = clamp(y + ctx.y, width);
|
|
372
|
+
const percentX = posX / width;
|
|
373
|
+
const percentY = posY / width;
|
|
374
|
+
|
|
375
|
+
const saturationX = Math.round(percentX * SB_MAX);
|
|
376
|
+
const brightnessY = Math.round(SB_MAX - percentY * SB_MAX);
|
|
377
|
+
|
|
378
|
+
saturation_handlePos.value = isRtl ? percentX * width - width + thumbSize / 2 : percentX * width - thumbSize / 2;
|
|
379
|
+
brightness_handlePos.value = percentY * width - thumbSize / 2;
|
|
380
|
+
|
|
381
|
+
runOnJS(updateSB)(saturationX, brightnessY);
|
|
382
|
+
},
|
|
383
|
+
onFinish: () => {
|
|
384
|
+
scale_panelHandle.value = withTiming(1, { duration: 100 });
|
|
385
|
+
runOnJS(onGestureEventFinish)();
|
|
386
|
+
},
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
return (
|
|
390
|
+
<PanGestureHandler onGestureEvent={panel_GestureEvent} minDist={0}>
|
|
391
|
+
<Animated.View style={[styles.panel_container, { width, height: width, borderRadius }, activeHueStyle]}>
|
|
392
|
+
<Image source={PANEL_IMAGE} style={[styles.panel_image, { borderRadius }]} />
|
|
393
|
+
<Animated.View
|
|
394
|
+
style={[styles.handle, { width: thumbSize, height: thumbSize, borderRadius: thumbSize / 2 }, panel_handleStyle]}
|
|
395
|
+
>
|
|
396
|
+
<Animated.View style={[styles.handleInner, { borderRadius: thumbSize / 2 }, previewColorWithoutOpacity]} />
|
|
397
|
+
</Animated.View>
|
|
398
|
+
</Animated.View>
|
|
399
|
+
</PanGestureHandler>
|
|
400
|
+
);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
export function Preview({
|
|
404
|
+
width,
|
|
405
|
+
initialColor,
|
|
406
|
+
returnedResults,
|
|
407
|
+
value,
|
|
408
|
+
previewColorStyle,
|
|
409
|
+
previewText,
|
|
410
|
+
resultTextColorST,
|
|
411
|
+
previewColorFormat,
|
|
412
|
+
style = {}, // by user
|
|
413
|
+
textStyle = {}, // by user
|
|
414
|
+
colorFormat = 'hex', // by user
|
|
415
|
+
hideInitialColor = false, // by user
|
|
416
|
+
}) {
|
|
417
|
+
previewColorFormat.current = colorFormat;
|
|
418
|
+
|
|
419
|
+
const initialColorText = useMemo(() => {
|
|
420
|
+
const { h, s, b } = initialColor.current;
|
|
421
|
+
const hsl = HSV_HSL(h, s, b);
|
|
422
|
+
const formated = returnedResults(initialColor.current)[previewColorFormat.current];
|
|
423
|
+
const color = hsl.l < 50 ? '#fff' : '#000';
|
|
424
|
+
return { formated, color };
|
|
425
|
+
}, [value, colorFormat]);
|
|
426
|
+
|
|
427
|
+
return (
|
|
428
|
+
<View style={[styles.preview, { width }, style]}>
|
|
429
|
+
{!hideInitialColor && (
|
|
430
|
+
<View style={[styles.previewChildren, { backgroundColor: value }]}>
|
|
431
|
+
<Text style={[{ color: initialColorText.color }, { fontWeight: 'bold' }, textStyle]}>{initialColorText.formated}</Text>
|
|
432
|
+
</View>
|
|
433
|
+
)}
|
|
434
|
+
<Animated.View style={[styles.previewChildren, previewColorStyle]}>
|
|
435
|
+
<ReText text={previewText} style={resultTextColorST} customStyle={textStyle} />
|
|
436
|
+
</Animated.View>
|
|
437
|
+
</View>
|
|
438
|
+
);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
export function HueSlider({
|
|
442
|
+
width,
|
|
443
|
+
hueThumbeSize,
|
|
444
|
+
setHandlesPos,
|
|
445
|
+
scale_hueHandle,
|
|
446
|
+
updateHue,
|
|
447
|
+
onGestureEventFinish,
|
|
448
|
+
hue_handlePos,
|
|
449
|
+
previewColorWithoutOpacity,
|
|
450
|
+
tracksHeight,
|
|
451
|
+
thumbsSize,
|
|
452
|
+
thumbSize = thumbsSize, // by user
|
|
453
|
+
trackHeight = tracksHeight, // by user
|
|
454
|
+
borderRadius = 5, // by user
|
|
455
|
+
}) {
|
|
456
|
+
trackHeight = trackHeight <= 80 ? trackHeight : 80;
|
|
457
|
+
hueThumbeSize.current = thumbSize;
|
|
458
|
+
|
|
459
|
+
useEffect(() => {
|
|
460
|
+
setHandlesPos();
|
|
461
|
+
}, []);
|
|
462
|
+
|
|
463
|
+
const hue_handleStyle = useAnimatedStyle(() => ({
|
|
464
|
+
backgroundColor: '#ffffff50',
|
|
465
|
+
borderColor: '#ffffff',
|
|
466
|
+
transform: [
|
|
467
|
+
{ translateX: hue_handlePos.value },
|
|
468
|
+
{ translateY: -(thumbSize - trackHeight) / 2 },
|
|
469
|
+
{ scale: scale_hueHandle.value },
|
|
470
|
+
],
|
|
471
|
+
}));
|
|
472
|
+
|
|
473
|
+
const HueGestureEvent = useAnimatedGestureHandler(
|
|
474
|
+
{
|
|
475
|
+
onStart: (event, ctx) => {
|
|
476
|
+
ctx.x = event.x;
|
|
477
|
+
scale_hueHandle.value = withTiming(HANDLES_SCALE, { duration: 100 });
|
|
478
|
+
},
|
|
479
|
+
onActive: (event, ctx) => {
|
|
480
|
+
const clamp = (v, max) => Math.min(Math.max(v, 0), max);
|
|
481
|
+
|
|
482
|
+
const x = event.translationX;
|
|
483
|
+
const pos = clamp(x + ctx.x, width);
|
|
484
|
+
const percent = pos / width;
|
|
485
|
+
|
|
486
|
+
const hueX = HUE_MAX - Math.round(percent * HUE_MAX);
|
|
487
|
+
|
|
488
|
+
hue_handlePos.value = isRtl ? percent * width - width + thumbSize / 2 : percent * width - thumbSize / 2;
|
|
489
|
+
|
|
490
|
+
runOnJS(updateHue)(hueX);
|
|
491
|
+
},
|
|
492
|
+
onFinish: () => {
|
|
493
|
+
scale_hueHandle.value = withTiming(1, { duration: 100 });
|
|
494
|
+
runOnJS(onGestureEventFinish)();
|
|
495
|
+
},
|
|
496
|
+
},
|
|
497
|
+
[width]
|
|
498
|
+
);
|
|
499
|
+
|
|
500
|
+
return (
|
|
501
|
+
<PanGestureHandler onGestureEvent={HueGestureEvent} minDist={0}>
|
|
502
|
+
<Animated.View style={{ position: 'relative', width, height: trackHeight }}>
|
|
503
|
+
<View style={[styles.hue_opacityImageContainer, { borderRadius }]}>
|
|
504
|
+
<Image source={HUE_IMAGE} style={styles.hue_opacityImage} />
|
|
505
|
+
</View>
|
|
506
|
+
<Animated.View
|
|
507
|
+
style={[styles.handle, { width: thumbSize, height: thumbSize, borderRadius: thumbSize / 2 }, hue_handleStyle]}
|
|
508
|
+
>
|
|
509
|
+
<Animated.View style={[styles.handleInner, { borderRadius: thumbSize / 2 }, previewColorWithoutOpacity]} />
|
|
510
|
+
</Animated.View>
|
|
511
|
+
</Animated.View>
|
|
512
|
+
</PanGestureHandler>
|
|
513
|
+
);
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
export function OpacitySlider({
|
|
517
|
+
width,
|
|
518
|
+
opacityThumbeSize,
|
|
519
|
+
setHandlesPos,
|
|
520
|
+
scale_opacityHandle,
|
|
521
|
+
activeHueStyle,
|
|
522
|
+
opacity_handlePos,
|
|
523
|
+
updateOpacity,
|
|
524
|
+
onGestureEventFinish,
|
|
525
|
+
previewColorWithoutOpacity,
|
|
526
|
+
tracksHeight,
|
|
527
|
+
thumbsSize,
|
|
528
|
+
thumbSize = thumbsSize, // by user
|
|
529
|
+
trackHeight = tracksHeight, // by user
|
|
530
|
+
borderRadius = 5, // by user
|
|
531
|
+
}) {
|
|
532
|
+
trackHeight = trackHeight <= 80 ? trackHeight : 80;
|
|
533
|
+
opacityThumbeSize.current = thumbSize;
|
|
534
|
+
|
|
535
|
+
useEffect(() => {
|
|
536
|
+
setHandlesPos();
|
|
537
|
+
}, []);
|
|
538
|
+
|
|
539
|
+
const opacity_handleStyle = useAnimatedStyle(() => ({
|
|
540
|
+
backgroundColor: '#ffffff50',
|
|
541
|
+
borderColor: '#ffffff',
|
|
542
|
+
transform: [
|
|
543
|
+
{ translateX: opacity_handlePos.value },
|
|
544
|
+
{ translateY: -(thumbSize - trackHeight) / 2 },
|
|
545
|
+
{ scale: scale_opacityHandle.value },
|
|
546
|
+
],
|
|
547
|
+
}));
|
|
548
|
+
|
|
549
|
+
const opacityGestureEvent = useAnimatedGestureHandler({
|
|
550
|
+
onStart: (event, ctx) => {
|
|
551
|
+
ctx.x = event.x;
|
|
552
|
+
scale_opacityHandle.value = withTiming(HANDLES_SCALE, { duration: 100 });
|
|
553
|
+
},
|
|
554
|
+
onActive: (event, ctx) => {
|
|
555
|
+
const clamp = (v, max) => Math.min(Math.max(v, 0), max);
|
|
556
|
+
|
|
557
|
+
const x = event.translationX;
|
|
558
|
+
const pos = clamp(x + ctx.x, width);
|
|
559
|
+
const percent = pos / width;
|
|
560
|
+
|
|
561
|
+
const opacityX = Math.round(percent * OPACITY_MAX);
|
|
562
|
+
|
|
563
|
+
opacity_handlePos.value = isRtl ? percent * width - width + thumbSize / 2 : percent * width - thumbSize / 2;
|
|
564
|
+
|
|
565
|
+
runOnJS(updateOpacity)(opacityX);
|
|
566
|
+
},
|
|
567
|
+
onFinish: () => {
|
|
568
|
+
scale_opacityHandle.value = withTiming(1, { duration: 100 });
|
|
569
|
+
runOnJS(onGestureEventFinish)();
|
|
570
|
+
},
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
return (
|
|
574
|
+
<PanGestureHandler onGestureEvent={opacityGestureEvent} minDist={0}>
|
|
575
|
+
<Animated.View style={{ position: 'relative', width, height: trackHeight }}>
|
|
576
|
+
<Animated.View style={[styles.hue_opacityImageContainer, { borderRadius }, activeHueStyle]}>
|
|
577
|
+
<Image source={OPACITY_IMAGE} style={styles.hue_opacityImage} />
|
|
578
|
+
</Animated.View>
|
|
579
|
+
<Animated.View
|
|
580
|
+
style={[styles.handle, { width: thumbSize, height: thumbSize, borderRadius: thumbSize / 2 }, opacity_handleStyle]}
|
|
581
|
+
>
|
|
582
|
+
<Animated.View style={[styles.handleInner, { borderRadius: thumbSize / 2 }, previewColorWithoutOpacity]} />
|
|
583
|
+
</Animated.View>
|
|
584
|
+
</Animated.View>
|
|
585
|
+
</PanGestureHandler>
|
|
586
|
+
);
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
export function Swatches({
|
|
590
|
+
width,
|
|
591
|
+
initialColor,
|
|
592
|
+
setHandlesPos,
|
|
593
|
+
onChange,
|
|
594
|
+
onComplete,
|
|
595
|
+
returnedResults,
|
|
596
|
+
colors, // by user
|
|
597
|
+
style = {}, // by user
|
|
598
|
+
swatcheStyle = {}, // by user
|
|
599
|
+
}) {
|
|
600
|
+
colors ??= SWATCHES_COLORS;
|
|
601
|
+
const onPress = swatch => {
|
|
602
|
+
initialColor.current = COLOR_HSVA(swatch);
|
|
603
|
+
setHandlesPos();
|
|
604
|
+
onChange?.(returnedResults());
|
|
605
|
+
onComplete?.(returnedResults());
|
|
606
|
+
};
|
|
607
|
+
return (
|
|
608
|
+
<View style={[styles.swatcheContainer, { width }, style]}>
|
|
609
|
+
{colors.map((swatch, i) => (
|
|
610
|
+
<Pressable
|
|
611
|
+
key={swatch + i}
|
|
612
|
+
onPress={() => onPress(swatch)}
|
|
613
|
+
style={[styles.swatch, swatcheStyle, { backgroundColor: swatch }]}
|
|
614
|
+
/>
|
|
615
|
+
))}
|
|
616
|
+
</View>
|
|
617
|
+
);
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
const styles = StyleSheet.create({
|
|
621
|
+
wrapper: {
|
|
622
|
+
flexDirection: 'column',
|
|
623
|
+
justifyContent: 'space-evenly',
|
|
624
|
+
alignItems: 'center',
|
|
625
|
+
alignSelf: 'center',
|
|
626
|
+
flex: 1,
|
|
627
|
+
},
|
|
628
|
+
panel_container: {
|
|
629
|
+
position: 'relative',
|
|
630
|
+
alignItems: 'center',
|
|
631
|
+
borderRadius: 5,
|
|
632
|
+
backgroundColor: 'red',
|
|
633
|
+
|
|
634
|
+
shadowColor: '#000',
|
|
635
|
+
shadowOffset: {
|
|
636
|
+
width: 0,
|
|
637
|
+
height: 2,
|
|
638
|
+
},
|
|
639
|
+
shadowOpacity: 0.25,
|
|
640
|
+
shadowRadius: 3.84,
|
|
641
|
+
|
|
642
|
+
elevation: 5,
|
|
643
|
+
},
|
|
644
|
+
panel_image: {
|
|
645
|
+
width: '100%',
|
|
646
|
+
height: '100%',
|
|
647
|
+
},
|
|
648
|
+
handle: {
|
|
649
|
+
position: 'absolute',
|
|
650
|
+
left: 0,
|
|
651
|
+
top: 0,
|
|
652
|
+
borderWidth: 1,
|
|
653
|
+
justifyContent: 'center',
|
|
654
|
+
alignItems: 'center',
|
|
655
|
+
},
|
|
656
|
+
handleInner: {
|
|
657
|
+
width: '75%',
|
|
658
|
+
height: '75%',
|
|
659
|
+
|
|
660
|
+
shadowColor: '#000',
|
|
661
|
+
shadowOffset: {
|
|
662
|
+
width: 0,
|
|
663
|
+
height: 2,
|
|
664
|
+
},
|
|
665
|
+
shadowOpacity: 0.25,
|
|
666
|
+
shadowRadius: 3.84,
|
|
667
|
+
|
|
668
|
+
elevation: 5,
|
|
669
|
+
},
|
|
670
|
+
hue_opacityImageContainer: {
|
|
671
|
+
width: '100%',
|
|
672
|
+
height: '100%',
|
|
673
|
+
borderRadius: 5,
|
|
674
|
+
overflow: 'hidden',
|
|
675
|
+
|
|
676
|
+
shadowColor: '#000',
|
|
677
|
+
shadowOffset: {
|
|
678
|
+
width: 0,
|
|
679
|
+
height: 2,
|
|
680
|
+
},
|
|
681
|
+
shadowOpacity: 0.25,
|
|
682
|
+
shadowRadius: 3.84,
|
|
683
|
+
|
|
684
|
+
elevation: 5,
|
|
685
|
+
},
|
|
686
|
+
hue_opacityImage: {
|
|
687
|
+
width: '100%',
|
|
688
|
+
height: 80,
|
|
689
|
+
},
|
|
690
|
+
preview: {
|
|
691
|
+
flexDirection: 'row',
|
|
692
|
+
height: TRACKS_HEIGHT,
|
|
693
|
+
borderRadius: 5,
|
|
694
|
+
overflow: 'hidden',
|
|
695
|
+
},
|
|
696
|
+
previewChildren: {
|
|
697
|
+
flex: 1,
|
|
698
|
+
height: '100%',
|
|
699
|
+
justifyContent: 'center',
|
|
700
|
+
alignItems: 'center',
|
|
701
|
+
},
|
|
702
|
+
swatcheContainer: {
|
|
703
|
+
flexDirection: 'row',
|
|
704
|
+
flexWrap: 'wrap',
|
|
705
|
+
justifyContent: 'space-between',
|
|
706
|
+
},
|
|
707
|
+
swatch: {
|
|
708
|
+
width: SWATCHE_SIZE,
|
|
709
|
+
height: SWATCHE_SIZE,
|
|
710
|
+
borderRadius: SWATCHE_SIZE / 2,
|
|
711
|
+
marginHorizontal: 5,
|
|
712
|
+
marginBottom: 15,
|
|
713
|
+
|
|
714
|
+
shadowColor: '#000',
|
|
715
|
+
shadowOffset: {
|
|
716
|
+
width: 0,
|
|
717
|
+
height: 2,
|
|
718
|
+
},
|
|
719
|
+
shadowOpacity: 0.25,
|
|
720
|
+
shadowRadius: 3.84,
|
|
721
|
+
|
|
722
|
+
elevation: 5,
|
|
723
|
+
},
|
|
724
|
+
});
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import NamedColors from './NamedColors';
|
|
2
|
+
|
|
3
|
+
export const HSL_RGB = (h, s, l) => {
|
|
4
|
+
s /= 100;
|
|
5
|
+
l /= 100;
|
|
6
|
+
|
|
7
|
+
if (h === undefined) return { r: 0, g: 0, b: 0 };
|
|
8
|
+
|
|
9
|
+
const chroma = (1 - Math.abs(2 * l - 1)) * s;
|
|
10
|
+
let huePrime = h / 60;
|
|
11
|
+
const secondComponent = chroma * (1 - Math.abs((huePrime % 2) - 1));
|
|
12
|
+
huePrime = Math.floor(huePrime);
|
|
13
|
+
|
|
14
|
+
let red = [0, 5].includes(huePrime) ? chroma : [1, 4].includes(huePrime) ? secondComponent : 0,
|
|
15
|
+
green = [1, 2].includes(huePrime) ? chroma : [0, 3].includes(huePrime) ? secondComponent : 0,
|
|
16
|
+
blue = [3, 4].includes(huePrime) ? chroma : [2, 5].includes(huePrime) ? secondComponent : 0;
|
|
17
|
+
|
|
18
|
+
const lightnessAdjustment = l - chroma / 2;
|
|
19
|
+
|
|
20
|
+
red += lightnessAdjustment;
|
|
21
|
+
green += lightnessAdjustment;
|
|
22
|
+
blue += lightnessAdjustment;
|
|
23
|
+
|
|
24
|
+
return { r: Math.abs(Math.round(red * 255)), g: Math.abs(Math.round(green * 255)), b: Math.abs(Math.round(blue * 255)) };
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const ALPHA_HEX = number => {
|
|
28
|
+
const op = Math.floor((number / 100) * 255);
|
|
29
|
+
const hex = op.toString(16);
|
|
30
|
+
return op < 16 ? '0' + hex : hex;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export const HSL_HEX = (h, s, l) => {
|
|
34
|
+
l /= 100;
|
|
35
|
+
const a = (s * Math.min(l, 1 - l)) / 100;
|
|
36
|
+
const f = n => {
|
|
37
|
+
const k = (n + h / 30) % 12;
|
|
38
|
+
const color = l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
|
|
39
|
+
return Math.round(255 * color)
|
|
40
|
+
.toString(16)
|
|
41
|
+
.padStart(2, '0'); // convert to Hex and prefix "0" if needed
|
|
42
|
+
};
|
|
43
|
+
return `#${f(0)}${f(8)}${f(4)}`;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export const HSV_HSL = (h, s, b) => {
|
|
47
|
+
s /= 100;
|
|
48
|
+
b /= 100;
|
|
49
|
+
let l = ((2 - s) * b) / 2;
|
|
50
|
+
if (l !== 0) s = Math.round((l === 1 ? 0 : l < 0.5 ? (s * b) / (l * 2) : (s * b) / (2 - l * 2)) * 100);
|
|
51
|
+
l = Math.round(l * 100);
|
|
52
|
+
return { h, s, l };
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export const RGB_HSV = (r, g, b) => {
|
|
56
|
+
let rabs, gabs, babs, rr, gg, bb, h, s, v, diff, diffc, percentRoundFn;
|
|
57
|
+
rabs = r / 255;
|
|
58
|
+
gabs = g / 255;
|
|
59
|
+
babs = b / 255;
|
|
60
|
+
// eslint-disable-next-line no-sequences
|
|
61
|
+
(v = Math.max(rabs, gabs, babs)), (diff = v - Math.min(rabs, gabs, babs));
|
|
62
|
+
diffc = c => (v - c) / 6 / diff + 1 / 2;
|
|
63
|
+
percentRoundFn = num => Math.round(num * 100) / 100;
|
|
64
|
+
if (diff === 0) {
|
|
65
|
+
h = s = 0;
|
|
66
|
+
} else {
|
|
67
|
+
s = diff / v;
|
|
68
|
+
rr = diffc(rabs);
|
|
69
|
+
gg = diffc(gabs);
|
|
70
|
+
bb = diffc(babs);
|
|
71
|
+
|
|
72
|
+
if (rabs === v) {
|
|
73
|
+
h = bb - gg;
|
|
74
|
+
} else if (gabs === v) {
|
|
75
|
+
h = 1 / 3 + rr - bb;
|
|
76
|
+
} else if (babs === v) {
|
|
77
|
+
h = 2 / 3 + gg - rr;
|
|
78
|
+
}
|
|
79
|
+
if (h < 0) {
|
|
80
|
+
h += 1;
|
|
81
|
+
} else if (h > 1) {
|
|
82
|
+
h -= 1;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
h: Math.round(h * 360),
|
|
87
|
+
s: percentRoundFn(s * 100),
|
|
88
|
+
b: percentRoundFn(v * 100),
|
|
89
|
+
};
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
export const HEX_RGB = hex => {
|
|
93
|
+
const isValidHex = h => /^#([A-Fa-f0-9]{3,4}){1,2}$/.test(h);
|
|
94
|
+
|
|
95
|
+
const getChunksFromString = (st, chunkSize) => st.match(new RegExp(`.{${chunkSize}}`, 'g'));
|
|
96
|
+
|
|
97
|
+
const convertHexUnitTo256 = hexStr => parseInt(hexStr.repeat(2 / hexStr.length), 16);
|
|
98
|
+
|
|
99
|
+
if (!isValidHex(hex)) throw new Error('Invalid HEX');
|
|
100
|
+
|
|
101
|
+
const chunkSize = Math.floor((hex.length - 1) / 3);
|
|
102
|
+
const hexArr = getChunksFromString(hex.slice(1), chunkSize);
|
|
103
|
+
const [r, g, b, a] = hexArr.map(convertHexUnitTo256);
|
|
104
|
+
const alpha = Math.round((a / 255) * 100);
|
|
105
|
+
return { r, g, b, a: alpha ? alpha : 100 };
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
export const HEX_HSV = hex => {
|
|
109
|
+
const { r, g, b, a } = HEX_RGB(hex);
|
|
110
|
+
return { ...RGB_HSV(r, g, b), a };
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
export const HSL_HSV = (h, s, l) => {
|
|
114
|
+
const { r, g, b } = HSL_RGB(h, s, l);
|
|
115
|
+
return RGB_HSV(r, g, b);
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
export const COLOR_HSVA = colorStr => {
|
|
119
|
+
colorStr = colorStr.trim();
|
|
120
|
+
const isRgb = colorStr.startsWith('rgb');
|
|
121
|
+
const isRgba = colorStr.startsWith('rgba');
|
|
122
|
+
const isHex = colorStr.startsWith('#');
|
|
123
|
+
const isNamedColor = NamedColors.hasOwnProperty(colorStr);
|
|
124
|
+
const isHsl = colorStr.startsWith('hsl');
|
|
125
|
+
const isHsla = colorStr.startsWith('hsla');
|
|
126
|
+
const isHsv = colorStr.startsWith('hsv');
|
|
127
|
+
const isHsva = colorStr.startsWith('hsva');
|
|
128
|
+
|
|
129
|
+
if (isRgb) {
|
|
130
|
+
const match = colorStr.match(/(?<=\()[^)]+/)?.[0];
|
|
131
|
+
if (!match) throw new Error('Invalid RGB value');
|
|
132
|
+
const colorValues = match.split(',');
|
|
133
|
+
if (colorValues.length !== 3) throw new Error('Invalid RGB value');
|
|
134
|
+
const [r, g, b] = colorValues.map(v => +v);
|
|
135
|
+
if (isNaN(r) || isNaN(g) || isNaN(b)) throw new Error('Invalid RGB value');
|
|
136
|
+
return { ...RGB_HSV(r, g, b), a: 100 };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (isRgba) {
|
|
140
|
+
const match = colorStr.match(/(?<=\()[^)]+/)?.[0];
|
|
141
|
+
if (!match) throw new Error('Invalid RGBA value');
|
|
142
|
+
const colorValues = match.split(',');
|
|
143
|
+
if (colorValues.length !== 4) throw new Error('Invalid RGBA value');
|
|
144
|
+
const [r, g, b, a] = colorValues.map(v => +v);
|
|
145
|
+
if (isNaN(r) || isNaN(g) || isNaN(b) || isNaN(a)) throw new Error('Invalid RGBA value');
|
|
146
|
+
return { ...RGB_HSV(r, g, b), a: Math.round(a * 100) };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (isHex) return HEX_HSV(colorStr);
|
|
150
|
+
|
|
151
|
+
if (isNamedColor) return HEX_HSV(NamedColors[colorStr]);
|
|
152
|
+
|
|
153
|
+
if (isHsl) {
|
|
154
|
+
const match = colorStr.match(/(?<=\()[^)]+/)?.[0];
|
|
155
|
+
if (!match) throw new Error('Invalid HSL value');
|
|
156
|
+
const colorValues = match.split(',');
|
|
157
|
+
if (colorValues.length !== 3) throw new Error('Invalid HSL value');
|
|
158
|
+
const [h, s, l] = colorValues.map(v => +v);
|
|
159
|
+
if (isNaN(h) || isNaN(s) || isNaN(l)) throw new Error('Invalid HSL value');
|
|
160
|
+
return { ...HSL_HSV(h, s, l), a: 100 };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (isHsla) {
|
|
164
|
+
const match = colorStr.match(/(?<=\()[^)]+/)?.[0];
|
|
165
|
+
if (!match) throw new Error('Invalid HSLA value');
|
|
166
|
+
const colorValues = match.split(',');
|
|
167
|
+
if (colorValues.length !== 4) throw new Error('Invalid HSLA value');
|
|
168
|
+
const [h, s, l, a] = colorValues.map(v => +v);
|
|
169
|
+
if (isNaN(h) || isNaN(s) || isNaN(l) || isNaN(a)) throw new Error('Invalid HSLA value');
|
|
170
|
+
return { ...HSL_HSV(h, s, l), a: Math.round(a * 100) };
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (isHsv) {
|
|
174
|
+
const match = colorStr.match(/(?<=\()[^)]+/)?.[0];
|
|
175
|
+
if (!match) throw new Error('Invalid HSV value');
|
|
176
|
+
const colorValues = match.split(',');
|
|
177
|
+
if (colorValues.length !== 3) throw new Error('Invalid HSV value');
|
|
178
|
+
const [h, s, b] = colorValues.map(n => +n);
|
|
179
|
+
if (isNaN(h) || isNaN(s) || isNaN(b)) throw new Error('Invalid HSV value');
|
|
180
|
+
return { h, s, b, a: 100 };
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (isHsva) {
|
|
184
|
+
const match = colorStr.match(/(?<=\()[^)]+/)?.[0];
|
|
185
|
+
if (!match) throw new Error('Invalid HSVA value');
|
|
186
|
+
const colorValues = match.split(',');
|
|
187
|
+
if (colorValues.length !== 4) throw new Error('Invalid HSVA value');
|
|
188
|
+
const [h, s, b, a] = colorValues.map(n => +n);
|
|
189
|
+
if (isNaN(h) || isNaN(s) || isNaN(b) || isNaN(a)) throw new Error('Invalid HSVA value');
|
|
190
|
+
return { h, s, b, a: Math.round(a * 100) };
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
throw new Error('Invalid color');
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
const luminanceRGB = (r, g, b) => {
|
|
197
|
+
const a = [r, g, b].map(v => {
|
|
198
|
+
v /= 255;
|
|
199
|
+
return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
|
|
200
|
+
});
|
|
201
|
+
return a[0] * 0.2126 + a[1] * 0.7152 + a[2] * 0.0722;
|
|
202
|
+
};
|
|
203
|
+
const luminanceHEX = hex => {
|
|
204
|
+
const { r, g, b } = HEX_RGB(hex);
|
|
205
|
+
const a = [r, g, b].map(v => {
|
|
206
|
+
v /= 255;
|
|
207
|
+
return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
|
|
208
|
+
});
|
|
209
|
+
return a[0] * 0.2126 + a[1] * 0.7152 + a[2] * 0.0722;
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
export const CONTRAST_RATIO = (h, s, v, hex) => {
|
|
213
|
+
const hsl = HSV_HSL(h, s, v),
|
|
214
|
+
{ r, g, b } = HSL_RGB(hsl.h, hsl.s, hsl.l),
|
|
215
|
+
lum1 = luminanceRGB(r, g, b),
|
|
216
|
+
lum2 = luminanceHEX(hex),
|
|
217
|
+
brightest = Math.max(lum1, lum2),
|
|
218
|
+
darkest = Math.min(lum1, lum2);
|
|
219
|
+
return +((brightest + 0.05) / (darkest + 0.05)).toFixed(1);
|
|
220
|
+
};
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2020 Doho
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/NamedColors.js
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
aliceblue: '#f0f8ff',
|
|
3
|
+
antiquewhite: '#faebd7',
|
|
4
|
+
aqua: '#00ffff',
|
|
5
|
+
aquamarine: '#7fffd4',
|
|
6
|
+
azure: '#f0ffff',
|
|
7
|
+
beige: '#f5f5dc',
|
|
8
|
+
bisque: '#ffe4c4',
|
|
9
|
+
black: '#000000',
|
|
10
|
+
blanchedalmond: '#ffebcd',
|
|
11
|
+
blue: '#0000ff',
|
|
12
|
+
blueviolet: '#8a2be2',
|
|
13
|
+
brown: '#a52a2a',
|
|
14
|
+
burlywood: '#deb887',
|
|
15
|
+
cadetblue: '#5f9ea0',
|
|
16
|
+
chartreuse: '#7fff00',
|
|
17
|
+
chocolate: '#d2691e',
|
|
18
|
+
coral: '#ff7f50',
|
|
19
|
+
cornflowerblue: '#6495ed',
|
|
20
|
+
cornsilk: '#fff8dc',
|
|
21
|
+
crimson: '#dc143c',
|
|
22
|
+
cyan: '#00ffff',
|
|
23
|
+
darkblue: '#00008b',
|
|
24
|
+
darkcyan: '#008b8b',
|
|
25
|
+
darkgoldenrod: '#b8860b',
|
|
26
|
+
darkgray: '#a9a9a9',
|
|
27
|
+
darkgreen: '#006400',
|
|
28
|
+
darkgrey: '#a9a9a9',
|
|
29
|
+
darkkhaki: '#bdb76b',
|
|
30
|
+
darkmagenta: '#8b008b',
|
|
31
|
+
darkolivegreen: '#556b2f',
|
|
32
|
+
darkorange: '#ff8c00',
|
|
33
|
+
darkorchid: '#9932cc',
|
|
34
|
+
darkred: '#8b0000',
|
|
35
|
+
darksalmon: '#e9967a',
|
|
36
|
+
darkseagreen: '#8fbc8f',
|
|
37
|
+
darkslateblue: '#483d8b',
|
|
38
|
+
darkslategrey: '#2f4f4f',
|
|
39
|
+
darkturquoise: '#00ced1',
|
|
40
|
+
darkviolet: '#9400d3',
|
|
41
|
+
deeppink: '#ff1493',
|
|
42
|
+
deepskyblue: '#00bfff',
|
|
43
|
+
dimgray: '#696969',
|
|
44
|
+
dimgrey: '#696969',
|
|
45
|
+
dodgerblue: '#1e90ff',
|
|
46
|
+
firebrick: '#b22222',
|
|
47
|
+
floralwhite: '#fffaf0',
|
|
48
|
+
forestgreen: '#228b22',
|
|
49
|
+
fuchsia: '#ff00ff',
|
|
50
|
+
gainsboro: '#dcdcdc',
|
|
51
|
+
ghostwhite: '#f8f8ff',
|
|
52
|
+
gold: '#ffd700',
|
|
53
|
+
goldenrod: '#daa520',
|
|
54
|
+
gray: '#808080',
|
|
55
|
+
green: '#008000',
|
|
56
|
+
greenyellow: '#adff2f',
|
|
57
|
+
grey: '#808080',
|
|
58
|
+
honeydew: '#f0fff0',
|
|
59
|
+
hotpink: '#ff69b4',
|
|
60
|
+
indianred: '#cd5c5c',
|
|
61
|
+
indigo: '#4b0082',
|
|
62
|
+
ivory: '#fffff0',
|
|
63
|
+
khaki: '#f0e68c',
|
|
64
|
+
lavender: '#e6e6fa',
|
|
65
|
+
lavenderblush: '#fff0f5',
|
|
66
|
+
lawngreen: '#7cfc00',
|
|
67
|
+
lemonchiffon: '#fffacd',
|
|
68
|
+
lightblue: '#add8e6',
|
|
69
|
+
lightcoral: '#f08080',
|
|
70
|
+
lightcyan: '#e0ffff',
|
|
71
|
+
lightgoldenrodyellow: '#fafad2',
|
|
72
|
+
lightgray: '#d3d3d3',
|
|
73
|
+
lightgreen: '#90ee90',
|
|
74
|
+
lightgrey: '#d3d3d3',
|
|
75
|
+
lightpink: '#ffb6c1',
|
|
76
|
+
lightsalmon: '#ffa07a',
|
|
77
|
+
lightseagreen: '#20b2aa',
|
|
78
|
+
lightskyblue: '#87cefa',
|
|
79
|
+
lightslategrey: '#778899',
|
|
80
|
+
lightsteelblue: '#b0c4de',
|
|
81
|
+
lightyellow: '#ffffe0',
|
|
82
|
+
lime: '#00ff00',
|
|
83
|
+
limegreen: '#32cd32',
|
|
84
|
+
linen: '#faf0e6',
|
|
85
|
+
magenta: '#ff00ff',
|
|
86
|
+
maroon: '#800000',
|
|
87
|
+
mediumaquamarine: '#66cdaa',
|
|
88
|
+
mediumblue: '#0000cd',
|
|
89
|
+
mediumorchid: '#ba55d3',
|
|
90
|
+
mediumpurple: '#9370db',
|
|
91
|
+
mediumseagreen: '#3cb371',
|
|
92
|
+
mediumslateblue: '#7b68ee',
|
|
93
|
+
mediumspringgreen: '#00fa9a',
|
|
94
|
+
mediumturquoise: '#48d1cc',
|
|
95
|
+
mediumvioletred: '#c71585',
|
|
96
|
+
midnightblue: '#191970',
|
|
97
|
+
mintcream: '#f5fffa',
|
|
98
|
+
mistyrose: '#ffe4e1',
|
|
99
|
+
moccasin: '#ffe4b5',
|
|
100
|
+
navajowhite: '#ffdead',
|
|
101
|
+
navy: '#000080',
|
|
102
|
+
oldlace: '#fdf5e6',
|
|
103
|
+
olive: '#808000',
|
|
104
|
+
olivedrab: '#6b8e23',
|
|
105
|
+
orange: '#ffa500',
|
|
106
|
+
orangered: '#ff4500',
|
|
107
|
+
orchid: '#da70d6',
|
|
108
|
+
palegoldenrod: '#eee8aa',
|
|
109
|
+
palegreen: '#98fb98',
|
|
110
|
+
paleturquoise: '#afeeee',
|
|
111
|
+
palevioletred: '#db7093',
|
|
112
|
+
papayawhip: '#ffefd5',
|
|
113
|
+
peachpuff: '#ffdab9',
|
|
114
|
+
peru: '#cd853f',
|
|
115
|
+
pink: '#ffc0cb',
|
|
116
|
+
plum: '#dda0dd',
|
|
117
|
+
powderblue: '#b0e0e6',
|
|
118
|
+
purple: '#800080',
|
|
119
|
+
rebeccapurple: '#663399',
|
|
120
|
+
red: '#ff0000',
|
|
121
|
+
rosybrown: '#bc8f8f',
|
|
122
|
+
royalblue: '#4169e1',
|
|
123
|
+
saddlebrown: '#8b4513',
|
|
124
|
+
salmon: '#fa8072',
|
|
125
|
+
sandybrown: '#f4a460',
|
|
126
|
+
seagreen: '#2e8b57',
|
|
127
|
+
seashell: '#fff5ee',
|
|
128
|
+
sienna: '#a0522d',
|
|
129
|
+
silver: '#c0c0c0',
|
|
130
|
+
skyblue: '#87ceeb',
|
|
131
|
+
slateblue: '#6a5acd',
|
|
132
|
+
slategray: '#708090',
|
|
133
|
+
snow: '#fffafa',
|
|
134
|
+
springgreen: '#00ff7f',
|
|
135
|
+
steelblue: '#4682b4',
|
|
136
|
+
tan: '#d2b48c',
|
|
137
|
+
teal: '#008080',
|
|
138
|
+
thistle: '#d8bfd8',
|
|
139
|
+
tomato: '#ff6347',
|
|
140
|
+
turquoise: '#40e0d0',
|
|
141
|
+
violet: '#ee82ee',
|
|
142
|
+
wheat: '#f5deb3',
|
|
143
|
+
white: '#ffffff',
|
|
144
|
+
whitesmoke: '#f5f5f5',
|
|
145
|
+
yellow: '#ffff00',
|
|
146
|
+
yellowgreen: '#9acd32',
|
|
147
|
+
};
|
|
Binary file
|
package/assets/Hue.png
ADDED
|
Binary file
|
|
Binary file
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { StyleProp, TextStyle, ViewStyle } from 'react-native';
|
|
2
|
+
|
|
3
|
+
interface returnedResults {
|
|
4
|
+
hex: string;
|
|
5
|
+
rgb: string;
|
|
6
|
+
rgba: string;
|
|
7
|
+
hsl: string;
|
|
8
|
+
hsla: string;
|
|
9
|
+
hsv: string;
|
|
10
|
+
hsva: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface ColorPickerProps {
|
|
14
|
+
/** - hue and opacity sliders track height. */
|
|
15
|
+
tracksHeight?: number;
|
|
16
|
+
|
|
17
|
+
/** - sliders handles (thumbs) size (height*width). */
|
|
18
|
+
thumbsSize?: number;
|
|
19
|
+
|
|
20
|
+
/** - color picker component width. */
|
|
21
|
+
width?: number;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* - color picker wrapper style.
|
|
25
|
+
* - if you want to change the width use the width prop.
|
|
26
|
+
*/
|
|
27
|
+
style?: StyleProp<ViewStyle>;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* - initial color.
|
|
31
|
+
* - Accepts `hex`, `rgb`, `rgba`, `hsl`, `hsla`, and `named color` formats.
|
|
32
|
+
*/
|
|
33
|
+
value?: string;
|
|
34
|
+
|
|
35
|
+
/** - called when the user moves the sliders. */
|
|
36
|
+
onChange?: (colors: returnedResults) => void;
|
|
37
|
+
|
|
38
|
+
/** - called when the user lifts his finger off the sliders. */
|
|
39
|
+
onComplete?: (colors: returnedResults) => void;
|
|
40
|
+
|
|
41
|
+
children?: React.ReactNode;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
interface SwatchesPorps {
|
|
45
|
+
/**
|
|
46
|
+
* - swatch style.
|
|
47
|
+
* - **Note** `backgroundColor` is immutable.
|
|
48
|
+
*/
|
|
49
|
+
swatcheStyle?: StyleProp<ViewStyle>;
|
|
50
|
+
|
|
51
|
+
/** - swatches container style. */
|
|
52
|
+
style?: StyleProp<ViewStyle>;
|
|
53
|
+
|
|
54
|
+
/** - provide your own swatches colors. */
|
|
55
|
+
colors?: string[];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
interface PreviewPorps {
|
|
59
|
+
/** - show color preview in specific format. */
|
|
60
|
+
colorFormat?: 'hex' | 'rgb' | 'rgba' | 'hsl' | 'hsla' | 'hsv' | 'hsva';
|
|
61
|
+
|
|
62
|
+
/** - hide initial color preview and show the picked color preview only. */
|
|
63
|
+
hideInitialColor?: boolean;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* - preview container style.
|
|
67
|
+
* - **Note** `backgroundColor` is immutable.
|
|
68
|
+
*/
|
|
69
|
+
style?: StyleProp<ViewStyle>;
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* - preview text style.
|
|
73
|
+
* - **Note** `color` is immutable.
|
|
74
|
+
*/
|
|
75
|
+
textStyle?: StyleProp<TextStyle>;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
interface PanelProps {
|
|
79
|
+
/** - panel border raduis. */
|
|
80
|
+
borderRadius?: number;
|
|
81
|
+
|
|
82
|
+
/** - panel handle (thumb) size (height*width). */
|
|
83
|
+
thumbSize?: number;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
interface HueProps {
|
|
87
|
+
/** - hue slider handle (thumb) size (height*width). */
|
|
88
|
+
thumbSize?: number;
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* - opacity slider track height.
|
|
92
|
+
* - **Note** max height is `80`
|
|
93
|
+
*/
|
|
94
|
+
trackHeight?: number;
|
|
95
|
+
|
|
96
|
+
/** - hue slider border raduis. */
|
|
97
|
+
borderRadius?: number;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
interface OpacityProps {
|
|
101
|
+
/** - opacity slider handle (thumb) size (height*width). */
|
|
102
|
+
thumbSize?: number;
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* - opacity slider track height.
|
|
106
|
+
* - **Note** max height is `80`
|
|
107
|
+
*/
|
|
108
|
+
trackHeight?: number;
|
|
109
|
+
|
|
110
|
+
/** - opacity slider border raduis. */
|
|
111
|
+
borderRadius?: number;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
declare const ColorPicker: React.FunctionComponent<ColorPickerProps>;
|
|
115
|
+
export declare const Preview: React.FunctionComponent<PreviewPorps>;
|
|
116
|
+
export declare const Panel: React.FunctionComponent<PanelProps>;
|
|
117
|
+
export declare const HueSlider: React.FunctionComponent<HueProps>;
|
|
118
|
+
export declare const OpacitySlider: React.FunctionComponent<OpacityProps>;
|
|
119
|
+
export declare const Swatches: React.FunctionComponent<SwatchesPorps>;
|
|
120
|
+
|
|
121
|
+
export default ColorPicker;
|
package/index.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "reanimated-color-picker",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"source": "index.js",
|
|
7
|
+
"types": "index.d.ts",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"publish": "npm publish"
|
|
10
|
+
},
|
|
11
|
+
"keywords": [
|
|
12
|
+
"react-native",
|
|
13
|
+
"color",
|
|
14
|
+
"picker",
|
|
15
|
+
"palette"
|
|
16
|
+
],
|
|
17
|
+
"repository": "https://github.com/alabsi91/rn-color-picker",
|
|
18
|
+
"author": "Ahmed Alabsi <alabsi91@gmail.com> (https://github.com/alabsi91)",
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"bugs": {
|
|
21
|
+
"url": "https://github.com/alabsi91/rn-color-picker/issues"
|
|
22
|
+
},
|
|
23
|
+
"homepage": "https://github.com/alabsi91/rn-color-picker",
|
|
24
|
+
"peerDependencies": {
|
|
25
|
+
"react-native-gesture-handler": ">=2.0.0",
|
|
26
|
+
"react-native-reanimated": ">=2.0.0"
|
|
27
|
+
}
|
|
28
|
+
}
|