react-native-color-picker-palette 1.0.0

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.
Files changed (38) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +340 -0
  3. package/package.json +70 -0
  4. package/src/core/hooks/index.ts +2 -0
  5. package/src/core/hooks/useColor.ts +93 -0
  6. package/src/core/hooks/useComponentLayout.ts +38 -0
  7. package/src/core/index.ts +19 -0
  8. package/src/core/services/ColorService.ts +338 -0
  9. package/src/core/services/index.ts +1 -0
  10. package/src/core/types/index.ts +211 -0
  11. package/src/core/utils/clamp.ts +16 -0
  12. package/src/core/utils/format.ts +59 -0
  13. package/src/core/utils/index.ts +8 -0
  14. package/src/full/components/Alpha.tsx +221 -0
  15. package/src/full/components/ColorPicker.tsx +206 -0
  16. package/src/full/components/Fields/HexField.tsx +125 -0
  17. package/src/full/components/Fields/RgbFields.tsx +192 -0
  18. package/src/full/components/Fields/index.tsx +70 -0
  19. package/src/full/components/Hue.tsx +188 -0
  20. package/src/full/components/RectangleSaturation.tsx +203 -0
  21. package/src/full/components/Saturation.tsx +258 -0
  22. package/src/full/components/Thumb.tsx +47 -0
  23. package/src/full/components/Value.tsx +192 -0
  24. package/src/full/components/index.ts +8 -0
  25. package/src/full/index.ts +69 -0
  26. package/src/index.ts +19 -0
  27. package/src/lite/components/Alpha.tsx +228 -0
  28. package/src/lite/components/ColorPicker.tsx +209 -0
  29. package/src/lite/components/Fields/HexField.tsx +103 -0
  30. package/src/lite/components/Fields/RgbFields.tsx +138 -0
  31. package/src/lite/components/Fields/index.tsx +53 -0
  32. package/src/lite/components/Hue.tsx +192 -0
  33. package/src/lite/components/RectangleSaturation.tsx +238 -0
  34. package/src/lite/components/Saturation.tsx +289 -0
  35. package/src/lite/components/Thumb.tsx +47 -0
  36. package/src/lite/components/Value.tsx +201 -0
  37. package/src/lite/components/index.ts +8 -0
  38. package/src/lite/index.ts +75 -0
@@ -0,0 +1,338 @@
1
+ import type { IColor, IRGB, IHSV, ColorModel } from '../types';
2
+ import { clamp, toHexString } from '../utils';
3
+
4
+ /**
5
+ * ColorService - Handles all color conversions between HEX, RGB, and HSV
6
+ *
7
+ * @example
8
+ * // Convert from hex
9
+ * const color = ColorService.fromHex('#FF5500');
10
+ *
11
+ * // Convert from RGB
12
+ * const color = ColorService.fromRgb({ r: 255, g: 85, b: 0, a: 1 });
13
+ *
14
+ * // Convert from HSV
15
+ * const color = ColorService.fromHsv({ h: 20, s: 100, v: 100, a: 1 });
16
+ *
17
+ * // Get string representations
18
+ * ColorService.toRgbString(color.rgb); // "rgb(255, 85, 0)"
19
+ * ColorService.toRgbaString(color.rgb); // "rgba(255, 85, 0, 1)"
20
+ */
21
+ export const ColorService = {
22
+ /**
23
+ * Convert from any color model to IColor (all formats)
24
+ *
25
+ * @param model - The color model type ('hex', 'rgb', or 'hsv')
26
+ * @param value - The color value in the specified model
27
+ * @returns Complete color object with all formats
28
+ */
29
+ convert<T extends ColorModel>(
30
+ model: T,
31
+ value: T extends 'hex' ? string : T extends 'rgb' ? IRGB : IHSV
32
+ ): IColor {
33
+ switch (model) {
34
+ case 'hex':
35
+ return ColorService.fromHex(value as string);
36
+ case 'rgb':
37
+ return ColorService.fromRgb(value as IRGB);
38
+ case 'hsv':
39
+ return ColorService.fromHsv(value as IHSV);
40
+ default:
41
+ throw new Error(`Unknown color model: ${model}`);
42
+ }
43
+ },
44
+
45
+ /**
46
+ * Create IColor from hex string
47
+ *
48
+ * @param hex - Hex color string (e.g., "#FF0000", "#F00", "#FF0000FF")
49
+ * @returns Complete color object
50
+ */
51
+ fromHex(hex: string): IColor {
52
+ const normalizedHex = ColorService.normalizeHex(hex);
53
+ const rgb = ColorService.hex2rgb(normalizedHex);
54
+ const hsv = ColorService.rgb2hsv(rgb);
55
+
56
+ return {
57
+ hex: normalizedHex,
58
+ rgb,
59
+ hsv,
60
+ };
61
+ },
62
+
63
+ /**
64
+ * Create IColor from RGB
65
+ *
66
+ * @param rgb - RGB color object
67
+ * @returns Complete color object
68
+ */
69
+ fromRgb(rgb: IRGB): IColor {
70
+ const normalizedRgb = ColorService.normalizeRgb(rgb);
71
+ const hex = ColorService.rgb2hex(normalizedRgb);
72
+ const hsv = ColorService.rgb2hsv(normalizedRgb);
73
+
74
+ return {
75
+ hex,
76
+ rgb: normalizedRgb,
77
+ hsv,
78
+ };
79
+ },
80
+
81
+ /**
82
+ * Create IColor from HSV
83
+ *
84
+ * @param hsv - HSV color object
85
+ * @returns Complete color object
86
+ */
87
+ fromHsv(hsv: IHSV): IColor {
88
+ const normalizedHsv = ColorService.normalizeHsv(hsv);
89
+ const rgb = ColorService.hsv2rgb(normalizedHsv);
90
+ const hex = ColorService.rgb2hex(rgb);
91
+
92
+ return {
93
+ hex,
94
+ rgb,
95
+ hsv: normalizedHsv,
96
+ };
97
+ },
98
+
99
+ /**
100
+ * Normalize hex string to #RRGGBB or #RRGGBBAA format
101
+ *
102
+ * @param hex - Raw hex string
103
+ * @returns Normalized hex string
104
+ */
105
+ normalizeHex(hex: string): string {
106
+ let h = hex.replace('#', '').toUpperCase();
107
+
108
+ // Handle short formats
109
+ if (h.length === 3) {
110
+ // #RGB -> #RRGGBB
111
+ h = h[0] + h[0] + h[1] + h[1] + h[2] + h[2];
112
+ } else if (h.length === 4) {
113
+ // #RGBA -> #RRGGBBAA
114
+ h = h[0] + h[0] + h[1] + h[1] + h[2] + h[2] + h[3] + h[3];
115
+ }
116
+
117
+ // Ensure minimum 6 characters
118
+ h = h.padEnd(6, '0');
119
+
120
+ return '#' + h.slice(0, 8); // Max 8 chars (RRGGBBAA)
121
+ },
122
+
123
+ /**
124
+ * Normalize RGB values to valid ranges
125
+ *
126
+ * @param rgb - Raw RGB object
127
+ * @returns Normalized RGB object
128
+ */
129
+ normalizeRgb(rgb: IRGB): IRGB {
130
+ return {
131
+ r: clamp(Math.round(rgb.r), 0, 255),
132
+ g: clamp(Math.round(rgb.g), 0, 255),
133
+ b: clamp(Math.round(rgb.b), 0, 255),
134
+ a: clamp(rgb.a ?? 1, 0, 1),
135
+ };
136
+ },
137
+
138
+ /**
139
+ * Normalize HSV values to valid ranges
140
+ *
141
+ * @param hsv - Raw HSV object
142
+ * @returns Normalized HSV object
143
+ */
144
+ normalizeHsv(hsv: IHSV): IHSV {
145
+ return {
146
+ h: clamp(hsv.h, 0, 360) % 360,
147
+ s: clamp(hsv.s, 0, 100),
148
+ v: clamp(hsv.v, 0, 100),
149
+ a: clamp(hsv.a ?? 1, 0, 1),
150
+ };
151
+ },
152
+
153
+ /**
154
+ * Convert HEX to RGB
155
+ *
156
+ * @param hex - Hex color string
157
+ * @returns RGB object
158
+ */
159
+ hex2rgb(hex: string): IRGB {
160
+ const h = hex.replace('#', '');
161
+
162
+ const r = parseInt(h.slice(0, 2), 16) || 0;
163
+ const g = parseInt(h.slice(2, 4), 16) || 0;
164
+ const b = parseInt(h.slice(4, 6), 16) || 0;
165
+ const a = h.length >= 8 ? parseInt(h.slice(6, 8), 16) / 255 : 1;
166
+
167
+ return { r, g, b, a };
168
+ },
169
+
170
+ /**
171
+ * Convert RGB to HEX
172
+ *
173
+ * @param rgb - RGB object
174
+ * @returns Hex string
175
+ */
176
+ rgb2hex(rgb: IRGB): string {
177
+ const r = toHexString(rgb.r);
178
+ const g = toHexString(rgb.g);
179
+ const b = toHexString(rgb.b);
180
+
181
+ let hex = `#${r}${g}${b}`.toUpperCase();
182
+
183
+ // Add alpha if not fully opaque
184
+ if (rgb.a !== undefined && rgb.a < 1) {
185
+ const a = toHexString(Math.round(rgb.a * 255));
186
+ hex += a.toUpperCase();
187
+ }
188
+
189
+ return hex;
190
+ },
191
+
192
+ /**
193
+ * Convert RGB to HSV
194
+ *
195
+ * @param rgb - RGB object
196
+ * @returns HSV object
197
+ */
198
+ rgb2hsv(rgb: IRGB): IHSV {
199
+ const r = rgb.r / 255;
200
+ const g = rgb.g / 255;
201
+ const b = rgb.b / 255;
202
+
203
+ const max = Math.max(r, g, b);
204
+ const min = Math.min(r, g, b);
205
+ const delta = max - min;
206
+
207
+ let h = 0;
208
+ let s = 0;
209
+ const v = max * 100;
210
+
211
+ if (delta !== 0) {
212
+ s = (delta / max) * 100;
213
+
214
+ switch (max) {
215
+ case r:
216
+ h = ((g - b) / delta + (g < b ? 6 : 0)) * 60;
217
+ break;
218
+ case g:
219
+ h = ((b - r) / delta + 2) * 60;
220
+ break;
221
+ case b:
222
+ h = ((r - g) / delta + 4) * 60;
223
+ break;
224
+ }
225
+ }
226
+
227
+ return {
228
+ h: Math.round(h),
229
+ s: Math.round(s),
230
+ v: Math.round(v),
231
+ a: rgb.a ?? 1,
232
+ };
233
+ },
234
+
235
+ /**
236
+ * Convert HSV to RGB
237
+ *
238
+ * @param hsv - HSV object
239
+ * @returns RGB object
240
+ */
241
+ hsv2rgb(hsv: IHSV): IRGB {
242
+ const h = hsv.h / 360;
243
+ const s = hsv.s / 100;
244
+ const v = hsv.v / 100;
245
+
246
+ let r = 0;
247
+ let g = 0;
248
+ let b = 0;
249
+
250
+ if (s === 0) {
251
+ r = g = b = v;
252
+ } else {
253
+ const i = Math.floor(h * 6);
254
+ const f = h * 6 - i;
255
+ const p = v * (1 - s);
256
+ const q = v * (1 - f * s);
257
+ const t = v * (1 - (1 - f) * s);
258
+
259
+ switch (i % 6) {
260
+ case 0:
261
+ r = v;
262
+ g = t;
263
+ b = p;
264
+ break;
265
+ case 1:
266
+ r = q;
267
+ g = v;
268
+ b = p;
269
+ break;
270
+ case 2:
271
+ r = p;
272
+ g = v;
273
+ b = t;
274
+ break;
275
+ case 3:
276
+ r = p;
277
+ g = q;
278
+ b = v;
279
+ break;
280
+ case 4:
281
+ r = t;
282
+ g = p;
283
+ b = v;
284
+ break;
285
+ case 5:
286
+ r = v;
287
+ g = p;
288
+ b = q;
289
+ break;
290
+ }
291
+ }
292
+
293
+ return {
294
+ r: Math.round(r * 255),
295
+ g: Math.round(g * 255),
296
+ b: Math.round(b * 255),
297
+ a: hsv.a ?? 1,
298
+ };
299
+ },
300
+
301
+ /**
302
+ * Get RGB string for styling
303
+ *
304
+ * @param rgb - RGB object
305
+ * @returns CSS rgb() string
306
+ */
307
+ toRgbString(rgb: IRGB): string {
308
+ return `rgb(${rgb.r}, ${rgb.g}, ${rgb.b})`;
309
+ },
310
+
311
+ /**
312
+ * Get RGBA string for styling
313
+ *
314
+ * @param rgb - RGB object
315
+ * @returns CSS rgba() string
316
+ */
317
+ toRgbaString(rgb: IRGB): string {
318
+ return `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${rgb.a})`;
319
+ },
320
+
321
+ /**
322
+ * Get HSL string from HSV (for gradients)
323
+ *
324
+ * @param hsv - HSV object
325
+ * @returns CSS hsl() string
326
+ */
327
+ toHslString(hsv: IHSV): string {
328
+ // Convert HSV to HSL
329
+ const h = hsv.h;
330
+ const s = hsv.s / 100;
331
+ const v = hsv.v / 100;
332
+
333
+ const l = v * (1 - s / 2);
334
+ const sl = l === 0 || l === 1 ? 0 : (v - l) / Math.min(l, 1 - l);
335
+
336
+ return `hsl(${h}, ${Math.round(sl * 100)}%, ${Math.round(l * 100)}%)`;
337
+ },
338
+ };
@@ -0,0 +1 @@
1
+ export { ColorService } from './ColorService';
@@ -0,0 +1,211 @@
1
+ /**
2
+ * RGB color representation
3
+ *
4
+ * @property r - Red channel (0-255)
5
+ * @property g - Green channel (0-255)
6
+ * @property b - Blue channel (0-255)
7
+ * @property a - Alpha channel (0-1)
8
+ */
9
+ export interface IRGB {
10
+ r: number;
11
+ g: number;
12
+ b: number;
13
+ a: number;
14
+ }
15
+
16
+ /**
17
+ * HSV color representation
18
+ *
19
+ * @property h - Hue (0-360)
20
+ * @property s - Saturation (0-100)
21
+ * @property v - Value/Brightness (0-100)
22
+ * @property a - Alpha channel (0-1)
23
+ */
24
+ export interface IHSV {
25
+ h: number;
26
+ s: number;
27
+ v: number;
28
+ a: number;
29
+ }
30
+
31
+ /**
32
+ * Complete color object with all formats
33
+ *
34
+ * @property hex - Hex string (e.g., "#FF0000" or "#FF0000FF" with alpha)
35
+ * @property rgb - RGB representation
36
+ * @property hsv - HSV representation
37
+ */
38
+ export interface IColor {
39
+ hex: string;
40
+ rgb: IRGB;
41
+ hsv: IHSV;
42
+ }
43
+
44
+ /**
45
+ * Color model types for conversion
46
+ */
47
+ export type ColorModel = 'hex' | 'rgb' | 'hsv';
48
+
49
+ /**
50
+ * Props for the main ColorPicker component
51
+ */
52
+ export interface IColorPickerProps {
53
+ /**
54
+ * Current color value
55
+ */
56
+ color: IColor;
57
+
58
+ /**
59
+ * Called on every color change during interaction
60
+ */
61
+ onChange: (color: IColor) => void;
62
+
63
+ /**
64
+ * Called when interaction ends (finger lifted)
65
+ */
66
+ onChangeComplete?: (color: IColor) => void;
67
+
68
+ /**
69
+ * Width of the picker (diameter for circle, width for rectangle)
70
+ * @default 250
71
+ */
72
+ width?: number;
73
+
74
+ /**
75
+ * Height of the hue and alpha bars
76
+ * @default 10
77
+ */
78
+ barHeight?: number;
79
+
80
+ /**
81
+ * Size of the thumb indicators
82
+ * @default 24
83
+ */
84
+ thumbSize?: number;
85
+
86
+ /**
87
+ * Hide the hue slider bar
88
+ * @default false
89
+ */
90
+ hideHue?: boolean;
91
+
92
+ /**
93
+ * Hide the alpha slider bar
94
+ * @default false
95
+ */
96
+ hideAlpha?: boolean;
97
+
98
+ /**
99
+ * Hide the color preview
100
+ * @default false
101
+ */
102
+ hidePreview?: boolean;
103
+
104
+ /**
105
+ * Hide the input fields
106
+ * @default false
107
+ */
108
+ hideInput?: boolean;
109
+
110
+ /**
111
+ * Disable all interactions
112
+ * @default false
113
+ */
114
+ disabled?: boolean;
115
+
116
+ /**
117
+ * Picker variant: 'circle' (color wheel) or 'rectangle' (SV rectangle)
118
+ * @default 'rectangle'
119
+ */
120
+ variant?: 'circle' | 'rectangle';
121
+ }
122
+
123
+ /**
124
+ * Props for the Saturation component (circular color wheel)
125
+ */
126
+ export interface ISaturationProps {
127
+ color: IColor;
128
+ onChange: (color: IColor) => void;
129
+ onChangeComplete?: (color: IColor) => void;
130
+ size: number;
131
+ thumbSize: number;
132
+ disabled?: boolean;
133
+ }
134
+
135
+ /**
136
+ * Props for the RectangleSaturation component
137
+ */
138
+ export interface IRectangleSaturationProps {
139
+ color: IColor;
140
+ onChange: (color: IColor) => void;
141
+ onChangeComplete?: (color: IColor) => void;
142
+ width: number;
143
+ height: number;
144
+ thumbSize: number;
145
+ disabled?: boolean;
146
+ }
147
+
148
+ /**
149
+ * Props for the Hue component
150
+ */
151
+ export interface IHueProps {
152
+ color: IColor;
153
+ onChange: (color: IColor) => void;
154
+ onChangeComplete?: (color: IColor) => void;
155
+ barHeight: number;
156
+ thumbSize: number;
157
+ disabled?: boolean;
158
+ }
159
+
160
+ /**
161
+ * Props for the Alpha component
162
+ */
163
+ export interface IAlphaProps {
164
+ color: IColor;
165
+ onChange: (color: IColor) => void;
166
+ onChangeComplete?: (color: IColor) => void;
167
+ barHeight: number;
168
+ thumbSize: number;
169
+ disabled?: boolean;
170
+ }
171
+
172
+ /**
173
+ * Props for the Value/Brightness component
174
+ */
175
+ export interface IValueProps {
176
+ color: IColor;
177
+ onChange: (color: IColor) => void;
178
+ onChangeComplete?: (color: IColor) => void;
179
+ barHeight: number;
180
+ thumbSize: number;
181
+ disabled?: boolean;
182
+ }
183
+
184
+ /**
185
+ * Props for the Thumb component
186
+ */
187
+ export interface IThumbProps {
188
+ size: number;
189
+ color?: string;
190
+ style?: object;
191
+ }
192
+
193
+ /**
194
+ * Props for input fields
195
+ */
196
+ export interface IFieldsProps {
197
+ color: IColor;
198
+ onChange: (color: IColor) => void;
199
+ onChangeComplete?: (color: IColor) => void;
200
+ disabled?: boolean;
201
+ }
202
+
203
+ /**
204
+ * Layout information from onLayout event
205
+ */
206
+ export interface ILayout {
207
+ width: number;
208
+ height: number;
209
+ x: number;
210
+ y: number;
211
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Clamps a value between a minimum and maximum
3
+ *
4
+ * @param value - The value to clamp
5
+ * @param min - Minimum allowed value
6
+ * @param max - Maximum allowed value
7
+ * @returns The clamped value
8
+ *
9
+ * @example
10
+ * clamp(150, 0, 100) // returns 100
11
+ * clamp(-10, 0, 100) // returns 0
12
+ * clamp(50, 0, 100) // returns 50
13
+ */
14
+ export const clamp = (value: number, min: number, max: number): number => {
15
+ return Math.min(Math.max(value, min), max);
16
+ };
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Pads a string with leading zeros
3
+ *
4
+ * @param str - The string to pad
5
+ * @param len - The target length
6
+ * @returns The padded string
7
+ */
8
+ export const padZero = (str: string, len: number = 2): string => {
9
+ return str.padStart(len, '0');
10
+ };
11
+
12
+ /**
13
+ * Converts a number to a two-digit hex string
14
+ *
15
+ * @param num - The number to convert (0-255)
16
+ * @returns Two-character hex string
17
+ *
18
+ * @example
19
+ * toHexString(255) // returns "FF"
20
+ * toHexString(0) // returns "00"
21
+ * toHexString(15) // returns "0F"
22
+ */
23
+ export const toHexString = (num: number): string => {
24
+ return padZero(Math.round(num).toString(16), 2);
25
+ };
26
+
27
+ /**
28
+ * Rounds a number to specified decimal places
29
+ *
30
+ * @param num - The number to round
31
+ * @param decimals - Number of decimal places
32
+ * @returns The rounded number
33
+ */
34
+ export const round = (num: number, decimals: number = 0): number => {
35
+ const factor = Math.pow(10, decimals);
36
+ return Math.round(num * factor) / factor;
37
+ };
38
+
39
+ /**
40
+ * Parses a float and returns 0 if NaN
41
+ *
42
+ * @param value - The string to parse
43
+ * @returns Parsed float or 0
44
+ */
45
+ export const safeParseFloat = (value: string): number => {
46
+ const parsed = parseFloat(value);
47
+ return isNaN(parsed) ? 0 : parsed;
48
+ };
49
+
50
+ /**
51
+ * Parses an int and returns 0 if NaN
52
+ *
53
+ * @param value - The string to parse
54
+ * @returns Parsed int or 0
55
+ */
56
+ export const safeParseInt = (value: string): number => {
57
+ const parsed = parseInt(value, 10);
58
+ return isNaN(parsed) ? 0 : parsed;
59
+ };
@@ -0,0 +1,8 @@
1
+ export { clamp } from './clamp';
2
+ export {
3
+ padZero,
4
+ toHexString,
5
+ round,
6
+ safeParseFloat,
7
+ safeParseInt,
8
+ } from './format';