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.
- package/LICENSE +21 -0
- package/README.md +340 -0
- package/package.json +70 -0
- package/src/core/hooks/index.ts +2 -0
- package/src/core/hooks/useColor.ts +93 -0
- package/src/core/hooks/useComponentLayout.ts +38 -0
- package/src/core/index.ts +19 -0
- package/src/core/services/ColorService.ts +338 -0
- package/src/core/services/index.ts +1 -0
- package/src/core/types/index.ts +211 -0
- package/src/core/utils/clamp.ts +16 -0
- package/src/core/utils/format.ts +59 -0
- package/src/core/utils/index.ts +8 -0
- package/src/full/components/Alpha.tsx +221 -0
- package/src/full/components/ColorPicker.tsx +206 -0
- package/src/full/components/Fields/HexField.tsx +125 -0
- package/src/full/components/Fields/RgbFields.tsx +192 -0
- package/src/full/components/Fields/index.tsx +70 -0
- package/src/full/components/Hue.tsx +188 -0
- package/src/full/components/RectangleSaturation.tsx +203 -0
- package/src/full/components/Saturation.tsx +258 -0
- package/src/full/components/Thumb.tsx +47 -0
- package/src/full/components/Value.tsx +192 -0
- package/src/full/components/index.ts +8 -0
- package/src/full/index.ts +69 -0
- package/src/index.ts +19 -0
- package/src/lite/components/Alpha.tsx +228 -0
- package/src/lite/components/ColorPicker.tsx +209 -0
- package/src/lite/components/Fields/HexField.tsx +103 -0
- package/src/lite/components/Fields/RgbFields.tsx +138 -0
- package/src/lite/components/Fields/index.tsx +53 -0
- package/src/lite/components/Hue.tsx +192 -0
- package/src/lite/components/RectangleSaturation.tsx +238 -0
- package/src/lite/components/Saturation.tsx +289 -0
- package/src/lite/components/Thumb.tsx +47 -0
- package/src/lite/components/Value.tsx +201 -0
- package/src/lite/components/index.ts +8 -0
- package/src/lite/index.ts +75 -0
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import React, { memo, useCallback, useMemo, useRef } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
View,
|
|
4
|
+
StyleSheet,
|
|
5
|
+
PanResponder,
|
|
6
|
+
GestureResponderEvent,
|
|
7
|
+
} from 'react-native';
|
|
8
|
+
import Svg, { Defs, RadialGradient, Stop, Circle, Path, G } from 'react-native-svg';
|
|
9
|
+
import type { ISaturationProps } from '../../core/types';
|
|
10
|
+
import { ColorService } from '../../core/services';
|
|
11
|
+
import { clamp } from '../../core/utils';
|
|
12
|
+
import { Thumb } from './Thumb';
|
|
13
|
+
|
|
14
|
+
// Number of segments for perfect color wheel (360 = 1° per segment)
|
|
15
|
+
const SEGMENT_COUNT = 360;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Circular Color Wheel picker component
|
|
19
|
+
*
|
|
20
|
+
* Features:
|
|
21
|
+
* - Uses 360 SVG segments (perfect 1° precision)
|
|
22
|
+
* - Caches measurements for better performance
|
|
23
|
+
* - Angle around circle = Hue (0-360°)
|
|
24
|
+
* - Distance from center = Saturation (0-100%)
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```tsx
|
|
28
|
+
* <Saturation
|
|
29
|
+
* color={color}
|
|
30
|
+
* onChange={handleChange}
|
|
31
|
+
* size={250}
|
|
32
|
+
* thumbSize={24}
|
|
33
|
+
* />
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
export const Saturation = memo(
|
|
37
|
+
({
|
|
38
|
+
color,
|
|
39
|
+
onChange,
|
|
40
|
+
onChangeComplete,
|
|
41
|
+
size,
|
|
42
|
+
thumbSize,
|
|
43
|
+
disabled = false,
|
|
44
|
+
}: ISaturationProps) => {
|
|
45
|
+
const containerRef = useRef<View>(null);
|
|
46
|
+
const measurementCache = useRef<{ x: number; y: number } | null>(null);
|
|
47
|
+
|
|
48
|
+
const radius = size / 2;
|
|
49
|
+
const effectiveRadius = radius - thumbSize / 2;
|
|
50
|
+
|
|
51
|
+
// Calculate thumb position from HSV values
|
|
52
|
+
const thumbPosition = useMemo(() => {
|
|
53
|
+
const angleRad = ((color.hsv.h - 90) * Math.PI) / 180;
|
|
54
|
+
const distance = (color.hsv.s / 100) * effectiveRadius;
|
|
55
|
+
|
|
56
|
+
const x = radius + Math.cos(angleRad) * distance - thumbSize / 2;
|
|
57
|
+
const y = radius + Math.sin(angleRad) * distance - thumbSize / 2;
|
|
58
|
+
|
|
59
|
+
return { x, y };
|
|
60
|
+
}, [color.hsv.h, color.hsv.s, radius, effectiveRadius, thumbSize]);
|
|
61
|
+
|
|
62
|
+
// Update measurement cache
|
|
63
|
+
const updateMeasurement = useCallback(() => {
|
|
64
|
+
return new Promise<{ x: number; y: number }>((resolve) => {
|
|
65
|
+
if (measurementCache.current) {
|
|
66
|
+
resolve(measurementCache.current);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
containerRef.current?.measureInWindow((x, y) => {
|
|
70
|
+
measurementCache.current = { x, y };
|
|
71
|
+
resolve({ x, y });
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
}, []);
|
|
75
|
+
|
|
76
|
+
// Convert touch coordinates to HSV
|
|
77
|
+
const updateColorFromPosition = useCallback(
|
|
78
|
+
async (pageX: number, pageY: number, isFinal: boolean) => {
|
|
79
|
+
const measurement = await updateMeasurement();
|
|
80
|
+
|
|
81
|
+
const x = pageX - measurement.x - radius;
|
|
82
|
+
const y = pageY - measurement.y - radius;
|
|
83
|
+
|
|
84
|
+
// Calculate angle (hue): 0° at top, clockwise
|
|
85
|
+
let angleDeg = (Math.atan2(y, x) * 180) / Math.PI + 90;
|
|
86
|
+
if (angleDeg < 0) angleDeg += 360;
|
|
87
|
+
|
|
88
|
+
// Calculate distance (saturation)
|
|
89
|
+
const distance = Math.min(Math.sqrt(x * x + y * y), effectiveRadius);
|
|
90
|
+
const saturation = (distance / effectiveRadius) * 100;
|
|
91
|
+
|
|
92
|
+
const nextColor = ColorService.convert('hsv', {
|
|
93
|
+
h: clamp(angleDeg, 0, 360),
|
|
94
|
+
s: clamp(saturation, 0, 100),
|
|
95
|
+
v: color.hsv.v,
|
|
96
|
+
a: color.hsv.a,
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
onChange(nextColor);
|
|
100
|
+
|
|
101
|
+
if (isFinal) {
|
|
102
|
+
onChangeComplete?.(nextColor);
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
[color.hsv.v, color.hsv.a, radius, effectiveRadius, onChange, onChangeComplete, updateMeasurement]
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
// Clear cache on layout change
|
|
109
|
+
const handleLayout = useCallback(() => {
|
|
110
|
+
measurementCache.current = null;
|
|
111
|
+
}, []);
|
|
112
|
+
|
|
113
|
+
// PanResponder for touch handling
|
|
114
|
+
const panResponder = useMemo(
|
|
115
|
+
() =>
|
|
116
|
+
PanResponder.create({
|
|
117
|
+
onStartShouldSetPanResponder: () => !disabled,
|
|
118
|
+
onMoveShouldSetPanResponder: () => !disabled,
|
|
119
|
+
onPanResponderGrant: (evt: GestureResponderEvent) => {
|
|
120
|
+
measurementCache.current = null;
|
|
121
|
+
updateColorFromPosition(
|
|
122
|
+
evt.nativeEvent.pageX,
|
|
123
|
+
evt.nativeEvent.pageY,
|
|
124
|
+
false
|
|
125
|
+
);
|
|
126
|
+
},
|
|
127
|
+
onPanResponderMove: (evt: GestureResponderEvent) => {
|
|
128
|
+
updateColorFromPosition(
|
|
129
|
+
evt.nativeEvent.pageX,
|
|
130
|
+
evt.nativeEvent.pageY,
|
|
131
|
+
false
|
|
132
|
+
);
|
|
133
|
+
},
|
|
134
|
+
onPanResponderRelease: (evt: GestureResponderEvent) => {
|
|
135
|
+
updateColorFromPosition(
|
|
136
|
+
evt.nativeEvent.pageX,
|
|
137
|
+
evt.nativeEvent.pageY,
|
|
138
|
+
true
|
|
139
|
+
);
|
|
140
|
+
},
|
|
141
|
+
}),
|
|
142
|
+
[disabled, updateColorFromPosition]
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
// Generate color wheel paths (360 segments - 1° per segment)
|
|
146
|
+
const colorWheelPaths = useMemo(() => {
|
|
147
|
+
const paths: { d: string; color: string }[] = [];
|
|
148
|
+
const angleStep = 360 / SEGMENT_COUNT;
|
|
149
|
+
|
|
150
|
+
for (let i = 0; i < SEGMENT_COUNT; i++) {
|
|
151
|
+
const startAngle = (i * angleStep - 90) * (Math.PI / 180);
|
|
152
|
+
const endAngle = ((i + 1) * angleStep - 90) * (Math.PI / 180);
|
|
153
|
+
|
|
154
|
+
const x1 = radius + Math.cos(startAngle) * radius;
|
|
155
|
+
const y1 = radius + Math.sin(startAngle) * radius;
|
|
156
|
+
const x2 = radius + Math.cos(endAngle) * radius;
|
|
157
|
+
const y2 = radius + Math.sin(endAngle) * radius;
|
|
158
|
+
|
|
159
|
+
// Use middle hue for the segment
|
|
160
|
+
const hue = (i + 0.5) * angleStep;
|
|
161
|
+
|
|
162
|
+
paths.push({
|
|
163
|
+
d: `M ${radius} ${radius} L ${x1} ${y1} A ${radius} ${radius} 0 0 1 ${x2} ${y2} Z`,
|
|
164
|
+
color: `hsl(${hue}, 100%, 50%)`,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return paths;
|
|
169
|
+
}, [radius]);
|
|
170
|
+
|
|
171
|
+
return (
|
|
172
|
+
<View
|
|
173
|
+
ref={containerRef}
|
|
174
|
+
onLayout={handleLayout}
|
|
175
|
+
style={[
|
|
176
|
+
styles.container,
|
|
177
|
+
{
|
|
178
|
+
width: size,
|
|
179
|
+
height: size,
|
|
180
|
+
opacity: disabled ? 0.5 : 1,
|
|
181
|
+
},
|
|
182
|
+
]}
|
|
183
|
+
{...panResponder.panHandlers}
|
|
184
|
+
>
|
|
185
|
+
<Svg width={size} height={size} viewBox={`0 0 ${size} ${size}`}>
|
|
186
|
+
<Defs>
|
|
187
|
+
<RadialGradient
|
|
188
|
+
id="saturationGradient"
|
|
189
|
+
cx="50%"
|
|
190
|
+
cy="50%"
|
|
191
|
+
rx="50%"
|
|
192
|
+
ry="50%"
|
|
193
|
+
>
|
|
194
|
+
<Stop offset="0%" stopColor="#FFFFFF" stopOpacity="1" />
|
|
195
|
+
<Stop offset="100%" stopColor="#FFFFFF" stopOpacity="0" />
|
|
196
|
+
</RadialGradient>
|
|
197
|
+
</Defs>
|
|
198
|
+
|
|
199
|
+
{/* Color wheel segments */}
|
|
200
|
+
<G>
|
|
201
|
+
{colorWheelPaths.map((segment, index) => (
|
|
202
|
+
<Path
|
|
203
|
+
key={index}
|
|
204
|
+
d={segment.d}
|
|
205
|
+
fill={segment.color}
|
|
206
|
+
stroke={segment.color}
|
|
207
|
+
strokeWidth={1}
|
|
208
|
+
/>
|
|
209
|
+
))}
|
|
210
|
+
</G>
|
|
211
|
+
|
|
212
|
+
{/* Saturation overlay (white center fading out) */}
|
|
213
|
+
<Circle
|
|
214
|
+
cx={radius}
|
|
215
|
+
cy={radius}
|
|
216
|
+
r={radius}
|
|
217
|
+
fill="url(#saturationGradient)"
|
|
218
|
+
/>
|
|
219
|
+
|
|
220
|
+
{/* Brightness overlay */}
|
|
221
|
+
<Circle
|
|
222
|
+
cx={radius}
|
|
223
|
+
cy={radius}
|
|
224
|
+
r={radius}
|
|
225
|
+
fill="#000000"
|
|
226
|
+
fillOpacity={1 - color.hsv.v / 100}
|
|
227
|
+
/>
|
|
228
|
+
</Svg>
|
|
229
|
+
|
|
230
|
+
{/* Thumb indicator with selected color */}
|
|
231
|
+
<View
|
|
232
|
+
style={[
|
|
233
|
+
styles.thumbContainer,
|
|
234
|
+
{
|
|
235
|
+
left: thumbPosition.x,
|
|
236
|
+
top: thumbPosition.y,
|
|
237
|
+
},
|
|
238
|
+
]}
|
|
239
|
+
pointerEvents="none"
|
|
240
|
+
>
|
|
241
|
+
<Thumb size={thumbSize} />
|
|
242
|
+
</View>
|
|
243
|
+
</View>
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
);
|
|
247
|
+
|
|
248
|
+
const styles = StyleSheet.create({
|
|
249
|
+
container: {
|
|
250
|
+
position: 'relative',
|
|
251
|
+
},
|
|
252
|
+
thumbContainer: {
|
|
253
|
+
position: 'absolute',
|
|
254
|
+
zIndex: 10,
|
|
255
|
+
},
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
Saturation.displayName = 'Saturation';
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import React, { memo } from 'react';
|
|
2
|
+
import { View, StyleSheet, ViewStyle } from 'react-native';
|
|
3
|
+
|
|
4
|
+
interface ThumbProps {
|
|
5
|
+
size: number;
|
|
6
|
+
color?: string;
|
|
7
|
+
style?: ViewStyle;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Thumb component - Circle indicator for color pickers
|
|
12
|
+
*
|
|
13
|
+
* Features:
|
|
14
|
+
* - Shows the selected color inside (if provided)
|
|
15
|
+
* - White 2px border for visibility on any background
|
|
16
|
+
* - Shadow for depth
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```tsx
|
|
20
|
+
* <Thumb size={24} color="#FF0000" />
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export const Thumb = memo(({ size, color, style }: ThumbProps) => {
|
|
24
|
+
const thumbStyle: ViewStyle = {
|
|
25
|
+
width: size,
|
|
26
|
+
height: size,
|
|
27
|
+
borderRadius: size / 2,
|
|
28
|
+
backgroundColor: color || 'transparent',
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
return <View style={[styles.thumb, thumbStyle, style]} />;
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const styles = StyleSheet.create({
|
|
35
|
+
thumb: {
|
|
36
|
+
borderWidth: 2,
|
|
37
|
+
borderColor: '#FFFFFF',
|
|
38
|
+
// Shadow for visibility on any background
|
|
39
|
+
shadowColor: '#000000',
|
|
40
|
+
shadowOffset: { width: 0, height: 1 },
|
|
41
|
+
shadowOpacity: 0.4,
|
|
42
|
+
shadowRadius: 2,
|
|
43
|
+
elevation: 4,
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
Thumb.displayName = 'Thumb';
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import React, { memo, useCallback, useMemo, useRef, useState } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
View,
|
|
4
|
+
StyleSheet,
|
|
5
|
+
PanResponder,
|
|
6
|
+
GestureResponderEvent,
|
|
7
|
+
LayoutChangeEvent,
|
|
8
|
+
} from 'react-native';
|
|
9
|
+
import Svg, { Defs, LinearGradient, Stop, Rect } from 'react-native-svg';
|
|
10
|
+
import type { IValueProps } from '../../core/types';
|
|
11
|
+
import { ColorService } from '../../core/services';
|
|
12
|
+
import { clamp } from '../../core/utils';
|
|
13
|
+
import { Thumb } from './Thumb';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Value/Brightness slider bar component
|
|
17
|
+
*
|
|
18
|
+
* Features:
|
|
19
|
+
* - Uses react-native-svg for smooth gradient
|
|
20
|
+
* - Horizontal bar from black to full brightness
|
|
21
|
+
* - Used with circle variant for brightness control
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```tsx
|
|
25
|
+
* <Value
|
|
26
|
+
* color={color}
|
|
27
|
+
* onChange={handleChange}
|
|
28
|
+
* barHeight={10}
|
|
29
|
+
* thumbSize={24}
|
|
30
|
+
* />
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export const Value = memo(
|
|
34
|
+
({
|
|
35
|
+
color,
|
|
36
|
+
onChange,
|
|
37
|
+
onChangeComplete,
|
|
38
|
+
barHeight,
|
|
39
|
+
thumbSize,
|
|
40
|
+
disabled = false,
|
|
41
|
+
}: IValueProps) => {
|
|
42
|
+
const containerRef = useRef<View>(null);
|
|
43
|
+
const [width, setWidth] = useState(1);
|
|
44
|
+
const measurementCache = useRef<number | null>(null);
|
|
45
|
+
|
|
46
|
+
// Calculate thumb position from value (v=100 should be at right)
|
|
47
|
+
const thumbPosition = useMemo(() => {
|
|
48
|
+
return (color.hsv.v / 100) * width;
|
|
49
|
+
}, [color.hsv.v, width]);
|
|
50
|
+
|
|
51
|
+
// Update measurement cache
|
|
52
|
+
const updateMeasurement = useCallback(() => {
|
|
53
|
+
return new Promise<number>((resolve) => {
|
|
54
|
+
if (measurementCache.current !== null) {
|
|
55
|
+
resolve(measurementCache.current);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
containerRef.current?.measureInWindow((x) => {
|
|
59
|
+
measurementCache.current = x;
|
|
60
|
+
resolve(x);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
}, []);
|
|
64
|
+
|
|
65
|
+
// Convert touch position to value
|
|
66
|
+
const updateColorFromPosition = useCallback(
|
|
67
|
+
async (pageX: number, isFinal: boolean) => {
|
|
68
|
+
const wx = await updateMeasurement();
|
|
69
|
+
const x = clamp(pageX - wx, 0, width);
|
|
70
|
+
const value = (x / width) * 100;
|
|
71
|
+
|
|
72
|
+
const nextColor = ColorService.convert('hsv', {
|
|
73
|
+
...color.hsv,
|
|
74
|
+
v: clamp(value, 0, 100),
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
onChange(nextColor);
|
|
78
|
+
|
|
79
|
+
if (isFinal) {
|
|
80
|
+
onChangeComplete?.(nextColor);
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
[color.hsv, width, onChange, onChangeComplete, updateMeasurement]
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
// PanResponder for touch handling
|
|
87
|
+
const panResponder = useMemo(
|
|
88
|
+
() =>
|
|
89
|
+
PanResponder.create({
|
|
90
|
+
onStartShouldSetPanResponder: () => !disabled,
|
|
91
|
+
onMoveShouldSetPanResponder: () => !disabled,
|
|
92
|
+
onPanResponderGrant: (evt: GestureResponderEvent) => {
|
|
93
|
+
measurementCache.current = null;
|
|
94
|
+
updateColorFromPosition(evt.nativeEvent.pageX, false);
|
|
95
|
+
},
|
|
96
|
+
onPanResponderMove: (evt: GestureResponderEvent) => {
|
|
97
|
+
updateColorFromPosition(evt.nativeEvent.pageX, false);
|
|
98
|
+
},
|
|
99
|
+
onPanResponderRelease: (evt: GestureResponderEvent) => {
|
|
100
|
+
updateColorFromPosition(evt.nativeEvent.pageX, true);
|
|
101
|
+
},
|
|
102
|
+
}),
|
|
103
|
+
[disabled, updateColorFromPosition]
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
// Handle layout to get width and clear cache
|
|
107
|
+
const handleLayout = useCallback((event: LayoutChangeEvent) => {
|
|
108
|
+
setWidth(event.nativeEvent.layout.width);
|
|
109
|
+
measurementCache.current = null;
|
|
110
|
+
}, []);
|
|
111
|
+
|
|
112
|
+
// Full brightness color for gradient end
|
|
113
|
+
const { h, s } = color.hsv;
|
|
114
|
+
const fullBrightnessColor = `hsl(${h}, ${s}%, ${50 - s / 4}%)`;
|
|
115
|
+
|
|
116
|
+
// Calculate thumb color
|
|
117
|
+
const thumbColor = useMemo(() => {
|
|
118
|
+
const { h, s, v } = color.hsv;
|
|
119
|
+
const l = (v / 200) * (200 - s);
|
|
120
|
+
return `hsl(${h}, ${s}%, ${l}%)`;
|
|
121
|
+
}, [color.hsv.h, color.hsv.s, color.hsv.v]);
|
|
122
|
+
|
|
123
|
+
return (
|
|
124
|
+
<View
|
|
125
|
+
ref={containerRef}
|
|
126
|
+
style={[
|
|
127
|
+
styles.container,
|
|
128
|
+
{
|
|
129
|
+
height: barHeight + thumbSize,
|
|
130
|
+
opacity: disabled ? 0.5 : 1,
|
|
131
|
+
},
|
|
132
|
+
]}
|
|
133
|
+
onLayout={handleLayout}
|
|
134
|
+
{...panResponder.panHandlers}
|
|
135
|
+
>
|
|
136
|
+
{/* Gradient bar using SVG */}
|
|
137
|
+
<View style={[styles.barContainer, { height: barHeight, top: thumbSize / 2 }]}>
|
|
138
|
+
<Svg width="100%" height={barHeight}>
|
|
139
|
+
<Defs>
|
|
140
|
+
<LinearGradient id="valueGradient" x1="0%" y1="0%" x2="100%" y2="0%">
|
|
141
|
+
<Stop offset="0%" stopColor="#000000" />
|
|
142
|
+
<Stop offset="100%" stopColor={fullBrightnessColor} />
|
|
143
|
+
</LinearGradient>
|
|
144
|
+
</Defs>
|
|
145
|
+
<Rect
|
|
146
|
+
x="0"
|
|
147
|
+
y="0"
|
|
148
|
+
width="100%"
|
|
149
|
+
height={barHeight}
|
|
150
|
+
rx={barHeight / 2}
|
|
151
|
+
ry={barHeight / 2}
|
|
152
|
+
fill="url(#valueGradient)"
|
|
153
|
+
/>
|
|
154
|
+
</Svg>
|
|
155
|
+
</View>
|
|
156
|
+
|
|
157
|
+
{/* Thumb indicator - centered vertically with bar */}
|
|
158
|
+
<View
|
|
159
|
+
style={[
|
|
160
|
+
styles.thumbContainer,
|
|
161
|
+
{
|
|
162
|
+
left: thumbPosition - thumbSize / 2,
|
|
163
|
+
top: barHeight / 2,
|
|
164
|
+
},
|
|
165
|
+
]}
|
|
166
|
+
pointerEvents="none"
|
|
167
|
+
>
|
|
168
|
+
<Thumb size={thumbSize} color={thumbColor} />
|
|
169
|
+
</View>
|
|
170
|
+
</View>
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
const styles = StyleSheet.create({
|
|
176
|
+
container: {
|
|
177
|
+
position: 'relative',
|
|
178
|
+
width: '100%',
|
|
179
|
+
},
|
|
180
|
+
barContainer: {
|
|
181
|
+
position: 'absolute',
|
|
182
|
+
left: 0,
|
|
183
|
+
right: 0,
|
|
184
|
+
overflow: 'hidden',
|
|
185
|
+
},
|
|
186
|
+
thumbContainer: {
|
|
187
|
+
position: 'absolute',
|
|
188
|
+
zIndex: 10,
|
|
189
|
+
},
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
Value.displayName = 'Value';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { ColorPicker } from './ColorPicker';
|
|
2
|
+
export { Saturation } from './Saturation';
|
|
3
|
+
export { RectangleSaturation } from './RectangleSaturation';
|
|
4
|
+
export { Hue } from './Hue';
|
|
5
|
+
export { Alpha } from './Alpha';
|
|
6
|
+
export { Value } from './Value';
|
|
7
|
+
export { Thumb } from './Thumb';
|
|
8
|
+
export { Fields, HexField, RgbFields } from './Fields';
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* React Native Color Palette - Full Version
|
|
3
|
+
*
|
|
4
|
+
* This version uses react-native-svg for smooth gradients and
|
|
5
|
+
* precise color rendering.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```tsx
|
|
9
|
+
* import { ColorPicker, useColor } from 'react-native-color-palette';
|
|
10
|
+
*
|
|
11
|
+
* function MyComponent() {
|
|
12
|
+
* const [color, setColor] = useColor('#FF0000');
|
|
13
|
+
*
|
|
14
|
+
* return (
|
|
15
|
+
* <ColorPicker
|
|
16
|
+
* color={color}
|
|
17
|
+
* onChange={setColor}
|
|
18
|
+
* onChangeComplete={(color) => console.log('Selected:', color.hex)}
|
|
19
|
+
* width={250}
|
|
20
|
+
* barHeight={10}
|
|
21
|
+
* thumbSize={24}
|
|
22
|
+
* hideAlpha={false}
|
|
23
|
+
* hideInput={false}
|
|
24
|
+
* />
|
|
25
|
+
* );
|
|
26
|
+
* }
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
// Main component
|
|
31
|
+
export { ColorPicker } from './components';
|
|
32
|
+
|
|
33
|
+
// Individual components for custom layouts
|
|
34
|
+
export {
|
|
35
|
+
Saturation,
|
|
36
|
+
RectangleSaturation,
|
|
37
|
+
Hue,
|
|
38
|
+
Alpha,
|
|
39
|
+
Value,
|
|
40
|
+
Thumb,
|
|
41
|
+
Fields,
|
|
42
|
+
HexField,
|
|
43
|
+
RgbFields,
|
|
44
|
+
} from './components';
|
|
45
|
+
|
|
46
|
+
// Re-export core utilities
|
|
47
|
+
export {
|
|
48
|
+
ColorService,
|
|
49
|
+
useColor,
|
|
50
|
+
useColorWithCallback,
|
|
51
|
+
useComponentLayout,
|
|
52
|
+
} from '../core';
|
|
53
|
+
|
|
54
|
+
// Re-export types
|
|
55
|
+
export type {
|
|
56
|
+
IColor,
|
|
57
|
+
IRGB,
|
|
58
|
+
IHSV,
|
|
59
|
+
ColorModel,
|
|
60
|
+
IColorPickerProps,
|
|
61
|
+
ISaturationProps,
|
|
62
|
+
IRectangleSaturationProps,
|
|
63
|
+
IHueProps,
|
|
64
|
+
IAlphaProps,
|
|
65
|
+
IValueProps,
|
|
66
|
+
IThumbProps,
|
|
67
|
+
IFieldsProps,
|
|
68
|
+
ILayout,
|
|
69
|
+
} from '../core';
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* React Native Color Palette
|
|
3
|
+
*
|
|
4
|
+
* A versatile color picker for React Native with two flavors:
|
|
5
|
+
* - Full version (default): Uses react-native-svg for smooth gradients
|
|
6
|
+
* - Lite version: Zero external dependencies, uses pure React Native Views
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```tsx
|
|
10
|
+
* // Full version (requires react-native-svg)
|
|
11
|
+
* import { ColorPicker, useColor } from 'react-native-color-palette';
|
|
12
|
+
*
|
|
13
|
+
* // Lite version (no external dependencies)
|
|
14
|
+
* import { ColorPicker, useColor } from 'react-native-color-palette/lite';
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
// Export everything from the full version as the default
|
|
19
|
+
export * from './full';
|