@darthrapid/react-native-color-picker 0.1.0 → 1.1.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/README.md +151 -33
- package/lib/module/components/alpha-strip.js +213 -0
- package/lib/module/components/alpha-strip.js.map +1 -0
- package/lib/module/components/color-picker.js +148 -40
- package/lib/module/components/color-picker.js.map +1 -1
- package/lib/module/components/palettes-tab.js +366 -0
- package/lib/module/components/palettes-tab.js.map +1 -0
- package/lib/module/components/picker-panel.js +115 -43
- package/lib/module/components/picker-panel.js.map +1 -1
- package/lib/module/components/picker-tab.js +12 -1
- package/lib/module/components/picker-tab.js.map +1 -1
- package/lib/module/components/tab-bar.js +51 -22
- package/lib/module/components/tab-bar.js.map +1 -1
- package/lib/module/components/values-tab.js +72 -7
- package/lib/module/components/values-tab.js.map +1 -1
- package/lib/module/index.js +1 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/shared/const.js +4 -5
- package/lib/module/shared/const.js.map +1 -1
- package/lib/module/shared/palletes.js +297 -0
- package/lib/module/shared/palletes.js.map +1 -0
- package/lib/module/utils/colors.js +33 -4
- package/lib/module/utils/colors.js.map +1 -1
- package/lib/typescript/src/components/alpha-strip.d.ts +11 -0
- package/lib/typescript/src/components/alpha-strip.d.ts.map +1 -0
- package/lib/typescript/src/components/color-picker.d.ts +16 -7
- package/lib/typescript/src/components/color-picker.d.ts.map +1 -1
- package/lib/typescript/src/components/palettes-tab.d.ts +19 -0
- package/lib/typescript/src/components/palettes-tab.d.ts.map +1 -0
- package/lib/typescript/src/components/picker-panel.d.ts +9 -5
- package/lib/typescript/src/components/picker-panel.d.ts.map +1 -1
- package/lib/typescript/src/components/picker-tab.d.ts +4 -1
- package/lib/typescript/src/components/picker-tab.d.ts.map +1 -1
- package/lib/typescript/src/components/tab-bar.d.ts.map +1 -1
- package/lib/typescript/src/components/values-tab.d.ts +2 -1
- package/lib/typescript/src/components/values-tab.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +3 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/shared/const.d.ts.map +1 -1
- package/lib/typescript/src/shared/palletes.d.ts +580 -0
- package/lib/typescript/src/shared/palletes.d.ts.map +1 -0
- package/lib/typescript/src/types/misc.d.ts +5 -6
- package/lib/typescript/src/types/misc.d.ts.map +1 -1
- package/lib/typescript/src/utils/colors.d.ts +4 -1
- package/lib/typescript/src/utils/colors.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/alpha-strip.tsx +212 -0
- package/src/components/color-picker.tsx +192 -56
- package/src/components/palettes-tab.tsx +358 -0
- package/src/components/picker-panel.tsx +104 -47
- package/src/components/picker-tab.tsx +17 -0
- package/src/components/tab-bar.tsx +60 -27
- package/src/components/values-tab.tsx +41 -6
- package/src/index.tsx +3 -1
- package/src/shared/const.ts +4 -5
- package/src/shared/palletes.ts +294 -0
- package/src/types/misc.ts +5 -6
- package/src/utils/colors.ts +34 -4
- package/lib/module/components/recent-tab.js +0 -89
- package/lib/module/components/recent-tab.js.map +0 -1
- package/lib/typescript/src/components/recent-tab.d.ts +0 -12
- package/lib/typescript/src/components/recent-tab.d.ts.map +0 -1
- package/src/components/recent-tab.tsx +0 -90
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import type React from "react";
|
|
2
|
+
import { useCallback, useRef, useState } from "react";
|
|
3
|
+
import { PanResponder, View } from "react-native";
|
|
4
|
+
import Svg, {
|
|
5
|
+
Defs,
|
|
6
|
+
LinearGradient,
|
|
7
|
+
Pattern,
|
|
8
|
+
Rect,
|
|
9
|
+
Stop,
|
|
10
|
+
} from "react-native-svg";
|
|
11
|
+
|
|
12
|
+
type AlphaStripProps = {
|
|
13
|
+
alpha: number;
|
|
14
|
+
color: string;
|
|
15
|
+
height: number;
|
|
16
|
+
disabled: boolean;
|
|
17
|
+
thumbBorder: string;
|
|
18
|
+
onChange: (alpha: number) => void;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export function AlphaStrip({
|
|
22
|
+
alpha,
|
|
23
|
+
color,
|
|
24
|
+
height,
|
|
25
|
+
disabled,
|
|
26
|
+
thumbBorder,
|
|
27
|
+
onChange,
|
|
28
|
+
}: AlphaStripProps) {
|
|
29
|
+
const containerRef = useRef<React.ComponentRef<typeof View>>(null);
|
|
30
|
+
const originXRef = useRef(0);
|
|
31
|
+
const widthRef = useRef(0);
|
|
32
|
+
const disabledRef = useRef(disabled);
|
|
33
|
+
const onChangeRef = useRef(onChange);
|
|
34
|
+
disabledRef.current = disabled;
|
|
35
|
+
onChangeRef.current = onChange;
|
|
36
|
+
|
|
37
|
+
const [layoutWidth, setLayoutWidth] = useState(0);
|
|
38
|
+
|
|
39
|
+
const calcAlpha = useCallback((pageX: number) => {
|
|
40
|
+
const w = widthRef.current;
|
|
41
|
+
if (w === 0) return 100;
|
|
42
|
+
const x = Math.max(0, Math.min(pageX - originXRef.current, w));
|
|
43
|
+
return Math.round((x / w) * 100);
|
|
44
|
+
}, []);
|
|
45
|
+
|
|
46
|
+
const panResponder = useRef(
|
|
47
|
+
PanResponder.create({
|
|
48
|
+
onStartShouldSetPanResponder: () => !disabledRef.current,
|
|
49
|
+
onStartShouldSetPanResponderCapture: () => !disabledRef.current,
|
|
50
|
+
onMoveShouldSetPanResponder: () => !disabledRef.current,
|
|
51
|
+
onMoveShouldSetPanResponderCapture: () => !disabledRef.current,
|
|
52
|
+
onPanResponderTerminationRequest: () => false,
|
|
53
|
+
onPanResponderGrant: (evt) => {
|
|
54
|
+
const { pageX } = evt.nativeEvent;
|
|
55
|
+
containerRef.current?.measure(
|
|
56
|
+
(_x: number, _y: number, w: number, _h: number, ox: number) => {
|
|
57
|
+
originXRef.current = ox;
|
|
58
|
+
widthRef.current = w;
|
|
59
|
+
onChangeRef.current(calcAlpha(pageX));
|
|
60
|
+
},
|
|
61
|
+
);
|
|
62
|
+
},
|
|
63
|
+
onPanResponderMove: (evt) => {
|
|
64
|
+
onChangeRef.current(calcAlpha(evt.nativeEvent.pageX));
|
|
65
|
+
},
|
|
66
|
+
}),
|
|
67
|
+
).current;
|
|
68
|
+
|
|
69
|
+
const thumbLeft = layoutWidth > 0 ? (alpha / 100) * layoutWidth : 0;
|
|
70
|
+
const thumbSize = height + 6;
|
|
71
|
+
const checkerSize = 5;
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
<View
|
|
75
|
+
ref={containerRef}
|
|
76
|
+
onLayout={(e) => {
|
|
77
|
+
const w = e.nativeEvent.layout.width;
|
|
78
|
+
setLayoutWidth(w);
|
|
79
|
+
widthRef.current = w;
|
|
80
|
+
}}
|
|
81
|
+
style={{ width: "100%", height, overflow: "visible" }}
|
|
82
|
+
{...panResponder.panHandlers}
|
|
83
|
+
>
|
|
84
|
+
{layoutWidth > 0 && (
|
|
85
|
+
<>
|
|
86
|
+
<Svg width={layoutWidth} height={height}>
|
|
87
|
+
<Defs>
|
|
88
|
+
<Pattern
|
|
89
|
+
id="alphaChecker"
|
|
90
|
+
x="0"
|
|
91
|
+
y="0"
|
|
92
|
+
width={checkerSize * 2}
|
|
93
|
+
height={checkerSize * 2}
|
|
94
|
+
patternUnits="userSpaceOnUse"
|
|
95
|
+
>
|
|
96
|
+
<Rect
|
|
97
|
+
x="0"
|
|
98
|
+
y="0"
|
|
99
|
+
width={checkerSize}
|
|
100
|
+
height={checkerSize}
|
|
101
|
+
fill="#CCCCCC"
|
|
102
|
+
/>
|
|
103
|
+
<Rect
|
|
104
|
+
x={checkerSize}
|
|
105
|
+
y="0"
|
|
106
|
+
width={checkerSize}
|
|
107
|
+
height={checkerSize}
|
|
108
|
+
fill="#FFFFFF"
|
|
109
|
+
/>
|
|
110
|
+
<Rect
|
|
111
|
+
x="0"
|
|
112
|
+
y={checkerSize}
|
|
113
|
+
width={checkerSize}
|
|
114
|
+
height={checkerSize}
|
|
115
|
+
fill="#FFFFFF"
|
|
116
|
+
/>
|
|
117
|
+
<Rect
|
|
118
|
+
x={checkerSize}
|
|
119
|
+
y={checkerSize}
|
|
120
|
+
width={checkerSize}
|
|
121
|
+
height={checkerSize}
|
|
122
|
+
fill="#CCCCCC"
|
|
123
|
+
/>
|
|
124
|
+
</Pattern>
|
|
125
|
+
<LinearGradient id="alphaGrad" x1="0" y1="0" x2="1" y2="0">
|
|
126
|
+
<Stop offset="0%" stopColor={color} stopOpacity={0} />
|
|
127
|
+
<Stop offset="100%" stopColor={color} stopOpacity={1} />
|
|
128
|
+
</LinearGradient>
|
|
129
|
+
</Defs>
|
|
130
|
+
{/* Checkerboard background */}
|
|
131
|
+
<Rect
|
|
132
|
+
x="0"
|
|
133
|
+
y="0"
|
|
134
|
+
width={layoutWidth}
|
|
135
|
+
height={height}
|
|
136
|
+
rx={height / 2}
|
|
137
|
+
fill="url(#alphaChecker)"
|
|
138
|
+
/>
|
|
139
|
+
{/* Alpha gradient overlay */}
|
|
140
|
+
<Rect
|
|
141
|
+
x="0"
|
|
142
|
+
y="0"
|
|
143
|
+
width={layoutWidth}
|
|
144
|
+
height={height}
|
|
145
|
+
rx={height / 2}
|
|
146
|
+
fill="url(#alphaGrad)"
|
|
147
|
+
/>
|
|
148
|
+
</Svg>
|
|
149
|
+
{/* Thumb with checkerboard background for alpha visualization */}
|
|
150
|
+
<View
|
|
151
|
+
style={{
|
|
152
|
+
position: "absolute",
|
|
153
|
+
left: thumbLeft - thumbSize / 2,
|
|
154
|
+
top: -3,
|
|
155
|
+
width: thumbSize,
|
|
156
|
+
height: thumbSize,
|
|
157
|
+
borderRadius: thumbSize / 2,
|
|
158
|
+
borderWidth: 3,
|
|
159
|
+
borderColor: thumbBorder,
|
|
160
|
+
overflow: "hidden",
|
|
161
|
+
shadowColor: "#000",
|
|
162
|
+
shadowOffset: { width: 0, height: 2 },
|
|
163
|
+
shadowOpacity: 0.3,
|
|
164
|
+
shadowRadius: 4,
|
|
165
|
+
elevation: 5,
|
|
166
|
+
}}
|
|
167
|
+
>
|
|
168
|
+
{/* Checkerboard background */}
|
|
169
|
+
<Svg
|
|
170
|
+
width={thumbSize}
|
|
171
|
+
height={thumbSize}
|
|
172
|
+
style={{ position: "absolute" }}
|
|
173
|
+
>
|
|
174
|
+
<Defs>
|
|
175
|
+
<Pattern
|
|
176
|
+
id="thumbChecker"
|
|
177
|
+
x="0"
|
|
178
|
+
y="0"
|
|
179
|
+
width={8}
|
|
180
|
+
height={8}
|
|
181
|
+
patternUnits="userSpaceOnUse"
|
|
182
|
+
>
|
|
183
|
+
<Rect x="0" y="0" width={4} height={4} fill="#CCCCCC" />
|
|
184
|
+
<Rect x={4} y="0" width={4} height={4} fill="#FFFFFF" />
|
|
185
|
+
<Rect x="0" y={4} width={4} height={4} fill="#FFFFFF" />
|
|
186
|
+
<Rect x={4} y={4} width={4} height={4} fill="#CCCCCC" />
|
|
187
|
+
</Pattern>
|
|
188
|
+
</Defs>
|
|
189
|
+
<Rect
|
|
190
|
+
x="0"
|
|
191
|
+
y="0"
|
|
192
|
+
width={thumbSize}
|
|
193
|
+
height={thumbSize}
|
|
194
|
+
fill="url(#thumbChecker)"
|
|
195
|
+
/>
|
|
196
|
+
</Svg>
|
|
197
|
+
{/* Color overlay with actual alpha */}
|
|
198
|
+
<View
|
|
199
|
+
style={{
|
|
200
|
+
position: "absolute",
|
|
201
|
+
width: "100%",
|
|
202
|
+
height: "100%",
|
|
203
|
+
backgroundColor: color,
|
|
204
|
+
opacity: alpha / 100,
|
|
205
|
+
}}
|
|
206
|
+
/>
|
|
207
|
+
</View>
|
|
208
|
+
</>
|
|
209
|
+
)}
|
|
210
|
+
</View>
|
|
211
|
+
);
|
|
212
|
+
}
|
|
@@ -7,24 +7,33 @@ import React, {
|
|
|
7
7
|
import {
|
|
8
8
|
Modal,
|
|
9
9
|
Pressable,
|
|
10
|
+
View,
|
|
10
11
|
type DimensionValue,
|
|
11
12
|
type StyleProp,
|
|
12
|
-
type ViewStyle
|
|
13
|
+
type ViewStyle,
|
|
13
14
|
} from "react-native";
|
|
15
|
+
import Svg, { Defs, Pattern, Rect } from "react-native-svg";
|
|
14
16
|
import { PickerPanel } from "./picker-panel";
|
|
15
17
|
import type { ColorPickerLabels, ColorPickerRef, TabId } from "../types/misc";
|
|
16
18
|
import { DEFAULT_LABELS, themes } from "../shared/const";
|
|
17
|
-
import {
|
|
19
|
+
import {
|
|
20
|
+
appendAlphaToHex,
|
|
21
|
+
getContrastColor,
|
|
22
|
+
hexToHsb,
|
|
23
|
+
hexToRgba,
|
|
24
|
+
hsbToHex,
|
|
25
|
+
isValidHex,
|
|
26
|
+
parseAlphaFromHex,
|
|
27
|
+
} from "../utils/colors";
|
|
28
|
+
import type { ColorPalette } from "./palettes-tab";
|
|
18
29
|
|
|
19
30
|
export type ColorPickerProps = {
|
|
20
|
-
/** Current color value (hex string) */
|
|
31
|
+
/** Current color value (hex string, 6 or 8 digit) */
|
|
21
32
|
value?: string;
|
|
22
|
-
/** Called when color changes */
|
|
33
|
+
/** Called when color changes (returns #RRGGBB or #RRGGBBAA when showAlpha is true) */
|
|
23
34
|
onChange?: (hex: string) => void;
|
|
24
|
-
/** Tabs to show (default: ["picker", "values", "
|
|
35
|
+
/** Tabs to show (default: ["picker", "values", "palettes"]) */
|
|
25
36
|
tabs?: TabId[];
|
|
26
|
-
/** Max recent colors (default: 16) */
|
|
27
|
-
maxRecentColors?: number;
|
|
28
37
|
/** Panel width in modal mode (default: 320). Accepts number or "100%". Ignored in inline mode. */
|
|
29
38
|
panelWidth?: DimensionValue;
|
|
30
39
|
/** Hue strip height (default: 28) */
|
|
@@ -45,8 +54,18 @@ export type ColorPickerProps = {
|
|
|
45
54
|
inline?: boolean;
|
|
46
55
|
/** Override default labels for i18n */
|
|
47
56
|
labels?: ColorPickerLabels;
|
|
48
|
-
/** Style for
|
|
49
|
-
|
|
57
|
+
/** Style for content wrapper */
|
|
58
|
+
contentStyle?: StyleProp<ViewStyle>;
|
|
59
|
+
/** Show alpha channel strip (default: false) */
|
|
60
|
+
showAlpha?: boolean;
|
|
61
|
+
/** Color palettes for the palettes tab */
|
|
62
|
+
palettes?: ColorPalette[];
|
|
63
|
+
/** Saved colors (controlled) - if provided, component won't manage saved colors internally */
|
|
64
|
+
savedColors?: string[];
|
|
65
|
+
/** Called when user saves a color */
|
|
66
|
+
onSaveColor?: (hex: string) => void;
|
|
67
|
+
/** Called when user clears saved colors */
|
|
68
|
+
onClearSaved?: () => void;
|
|
50
69
|
};
|
|
51
70
|
|
|
52
71
|
export const ColorPicker = React.forwardRef<ColorPickerRef, ColorPickerProps>(
|
|
@@ -54,8 +73,7 @@ export const ColorPicker = React.forwardRef<ColorPickerRef, ColorPickerProps>(
|
|
|
54
73
|
{
|
|
55
74
|
value = "#007AFF",
|
|
56
75
|
onChange,
|
|
57
|
-
tabs = ["picker", "values", "
|
|
58
|
-
maxRecentColors = 16,
|
|
76
|
+
tabs = ["picker", "values", "palettes"],
|
|
59
77
|
panelWidth = "100%",
|
|
60
78
|
hueStripHeight = 28,
|
|
61
79
|
theme: themeName = "dark",
|
|
@@ -66,7 +84,12 @@ export const ColorPicker = React.forwardRef<ColorPickerRef, ColorPickerProps>(
|
|
|
66
84
|
swatchStyle,
|
|
67
85
|
inline = false,
|
|
68
86
|
labels: userLabels,
|
|
69
|
-
|
|
87
|
+
contentStyle,
|
|
88
|
+
showAlpha = false,
|
|
89
|
+
palettes,
|
|
90
|
+
savedColors: savedColorsProp,
|
|
91
|
+
onSaveColor: onSaveColorProp,
|
|
92
|
+
onClearSaved: onClearSavedProp,
|
|
70
93
|
},
|
|
71
94
|
ref,
|
|
72
95
|
) => {
|
|
@@ -78,19 +101,29 @@ export const ColorPicker = React.forwardRef<ColorPickerRef, ColorPickerProps>(
|
|
|
78
101
|
const [sat, setSat] = useState(initial.s);
|
|
79
102
|
const [bright, setBright] = useState(initial.b);
|
|
80
103
|
const [hexInput, setHexInput] = useState(value.toUpperCase());
|
|
81
|
-
const [
|
|
104
|
+
const [alpha, setAlpha] = useState(
|
|
105
|
+
showAlpha ? parseAlphaFromHex(value) : 100,
|
|
106
|
+
);
|
|
107
|
+
// Internal saved colors state (used when not controlled)
|
|
108
|
+
const [internalSavedColors, setInternalSavedColors] = useState<string[]>([]);
|
|
82
109
|
const [modalVisible, setModalVisible] = useState(false);
|
|
83
110
|
|
|
111
|
+
// Use controlled or internal saved colors
|
|
112
|
+
const isControlled = savedColorsProp !== undefined;
|
|
113
|
+
const savedColors = isControlled ? savedColorsProp : internalSavedColors;
|
|
114
|
+
|
|
84
115
|
const currentHex = hsbToHex(hue, sat, bright);
|
|
85
|
-
const contrastColor = getContrastColor(currentHex);
|
|
116
|
+
const contrastColor = getContrastColor(currentHex, showAlpha ? alpha : 100);
|
|
86
117
|
|
|
87
118
|
// Refs for stable handler closures
|
|
88
119
|
const hueRef = useRef(hue);
|
|
89
120
|
const satRef = useRef(sat);
|
|
90
121
|
const brightRef = useRef(bright);
|
|
122
|
+
const alphaRef = useRef(alpha);
|
|
91
123
|
hueRef.current = hue;
|
|
92
124
|
satRef.current = sat;
|
|
93
125
|
brightRef.current = bright;
|
|
126
|
+
alphaRef.current = alpha;
|
|
94
127
|
|
|
95
128
|
// Sync hex input display — only update when NOT focused
|
|
96
129
|
const hexInputFocusedRef = useRef(false);
|
|
@@ -113,27 +146,44 @@ export const ColorPicker = React.forwardRef<ColorPickerRef, ColorPickerProps>(
|
|
|
113
146
|
setSat(hsb.s);
|
|
114
147
|
setBright(hsb.b);
|
|
115
148
|
}
|
|
149
|
+
if (showAlpha) {
|
|
150
|
+
const a = parseAlphaFromHex(value);
|
|
151
|
+
if (a !== alpha) setAlpha(a);
|
|
152
|
+
}
|
|
116
153
|
}
|
|
117
154
|
}
|
|
118
155
|
|
|
119
156
|
const notifyChange = useCallback(
|
|
120
|
-
(h: number, s: number, b: number) => {
|
|
157
|
+
(h: number, s: number, b: number, a?: number) => {
|
|
121
158
|
const hex = hsbToHex(h, s, b);
|
|
122
|
-
|
|
123
|
-
|
|
159
|
+
const result = showAlpha
|
|
160
|
+
? appendAlphaToHex(hex, a ?? alphaRef.current)
|
|
161
|
+
: hex;
|
|
162
|
+
lastExternalValue.current = result; // prevent sync-back
|
|
163
|
+
onChange?.(result);
|
|
124
164
|
},
|
|
125
|
-
[onChange],
|
|
165
|
+
[onChange, showAlpha],
|
|
126
166
|
);
|
|
127
167
|
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
168
|
+
const handleSaveColor = useCallback(() => {
|
|
169
|
+
const hex = showAlpha ? appendAlphaToHex(currentHex, alpha) : currentHex;
|
|
170
|
+
if (isControlled) {
|
|
171
|
+
onSaveColorProp?.(hex);
|
|
172
|
+
} else {
|
|
173
|
+
setInternalSavedColors((prev) => {
|
|
174
|
+
if (prev.includes(hex)) return prev;
|
|
175
|
+
return [hex, ...prev];
|
|
133
176
|
});
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
|
|
177
|
+
}
|
|
178
|
+
}, [currentHex, alpha, showAlpha, isControlled, onSaveColorProp]);
|
|
179
|
+
|
|
180
|
+
const handleClearSaved = useCallback(() => {
|
|
181
|
+
if (isControlled) {
|
|
182
|
+
onClearSavedProp?.();
|
|
183
|
+
} else {
|
|
184
|
+
setInternalSavedColors([]);
|
|
185
|
+
}
|
|
186
|
+
}, [isControlled, onClearSavedProp]);
|
|
137
187
|
|
|
138
188
|
const handleHueChange = useCallback(
|
|
139
189
|
(h: number) => {
|
|
@@ -152,6 +202,14 @@ export const ColorPicker = React.forwardRef<ColorPickerRef, ColorPickerProps>(
|
|
|
152
202
|
[notifyChange],
|
|
153
203
|
);
|
|
154
204
|
|
|
205
|
+
const handleAlphaChange = useCallback(
|
|
206
|
+
(a: number) => {
|
|
207
|
+
setAlpha(a);
|
|
208
|
+
notifyChange(hueRef.current, satRef.current, brightRef.current, a);
|
|
209
|
+
},
|
|
210
|
+
[notifyChange],
|
|
211
|
+
);
|
|
212
|
+
|
|
155
213
|
const handleHexSubmit = useCallback(() => {
|
|
156
214
|
const clean = hexInput.startsWith("#") ? hexInput : `#${hexInput}`;
|
|
157
215
|
if (isValidHex(clean)) {
|
|
@@ -160,21 +218,26 @@ export const ColorPicker = React.forwardRef<ColorPickerRef, ColorPickerProps>(
|
|
|
160
218
|
setSat(hsb.s);
|
|
161
219
|
setBright(hsb.b);
|
|
162
220
|
notifyChange(hsb.h, hsb.s, hsb.b);
|
|
163
|
-
addToRecent(clean);
|
|
164
221
|
} else {
|
|
165
222
|
setHexInput(currentHex.toUpperCase());
|
|
166
223
|
}
|
|
167
|
-
}, [hexInput, currentHex, notifyChange
|
|
224
|
+
}, [hexInput, currentHex, notifyChange]);
|
|
168
225
|
|
|
169
|
-
const
|
|
226
|
+
const handleSelectColor = useCallback(
|
|
170
227
|
(hex: string) => {
|
|
171
228
|
const hsb = hexToHsb(hex);
|
|
172
229
|
setHue(hsb.h);
|
|
173
230
|
setSat(hsb.s);
|
|
174
231
|
setBright(hsb.b);
|
|
175
|
-
|
|
232
|
+
if (showAlpha) {
|
|
233
|
+
const a = parseAlphaFromHex(hex);
|
|
234
|
+
setAlpha(a);
|
|
235
|
+
notifyChange(hsb.h, hsb.s, hsb.b, a);
|
|
236
|
+
} else {
|
|
237
|
+
notifyChange(hsb.h, hsb.s, hsb.b);
|
|
238
|
+
}
|
|
176
239
|
},
|
|
177
|
-
[notifyChange],
|
|
240
|
+
[notifyChange, showAlpha],
|
|
178
241
|
);
|
|
179
242
|
|
|
180
243
|
useImperativeHandle(
|
|
@@ -190,21 +253,22 @@ export const ColorPicker = React.forwardRef<ColorPickerRef, ColorPickerProps>(
|
|
|
190
253
|
lastExternalValue.current = hex;
|
|
191
254
|
notifyChange(hsb.h, hsb.s, hsb.b);
|
|
192
255
|
},
|
|
193
|
-
|
|
256
|
+
clearSaved: () => handleClearSaved(),
|
|
194
257
|
open: () => setModalVisible(true),
|
|
195
258
|
close: () => setModalVisible(false),
|
|
196
259
|
}),
|
|
197
|
-
[currentHex, notifyChange],
|
|
260
|
+
[currentHex, notifyChange, handleClearSaved],
|
|
198
261
|
);
|
|
199
262
|
|
|
200
263
|
const panelProps = {
|
|
201
264
|
hue,
|
|
202
265
|
sat,
|
|
203
266
|
bright,
|
|
267
|
+
...(showAlpha ? { alpha, onAlphaChange: handleAlphaChange } : {}),
|
|
204
268
|
currentHex,
|
|
205
269
|
contrastColor,
|
|
206
270
|
hexInput,
|
|
207
|
-
|
|
271
|
+
savedColors,
|
|
208
272
|
tabs,
|
|
209
273
|
hueStripHeight,
|
|
210
274
|
disabled,
|
|
@@ -220,15 +284,19 @@ export const ColorPicker = React.forwardRef<ColorPickerRef, ColorPickerProps>(
|
|
|
220
284
|
onHexInputBlur: () => {
|
|
221
285
|
hexInputFocusedRef.current = false;
|
|
222
286
|
},
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
287
|
+
palettes,
|
|
288
|
+
onSaveColor: handleSaveColor,
|
|
289
|
+
onSelectColor: handleSelectColor,
|
|
290
|
+
onClearSaved: isControlled ? onClearSavedProp : handleClearSaved,
|
|
226
291
|
};
|
|
227
292
|
|
|
228
293
|
if (inline) {
|
|
229
294
|
return <PickerPanel {...panelProps} style={style} />;
|
|
230
295
|
}
|
|
231
296
|
|
|
297
|
+
const swatchColor = showAlpha ? hexToRgba(currentHex, alpha) : currentHex;
|
|
298
|
+
const checkerSize = 6;
|
|
299
|
+
|
|
232
300
|
return (
|
|
233
301
|
<>
|
|
234
302
|
<Pressable
|
|
@@ -239,7 +307,7 @@ export const ColorPicker = React.forwardRef<ColorPickerRef, ColorPickerProps>(
|
|
|
239
307
|
width: swatchSize,
|
|
240
308
|
height: swatchSize,
|
|
241
309
|
borderRadius: swatchBorderRadius,
|
|
242
|
-
|
|
310
|
+
overflow: "hidden",
|
|
243
311
|
borderWidth: 2,
|
|
244
312
|
borderColor: t.border,
|
|
245
313
|
shadowColor: "#000",
|
|
@@ -250,7 +318,70 @@ export const ColorPicker = React.forwardRef<ColorPickerRef, ColorPickerProps>(
|
|
|
250
318
|
},
|
|
251
319
|
swatchStyle,
|
|
252
320
|
]}
|
|
253
|
-
|
|
321
|
+
>
|
|
322
|
+
{showAlpha && (
|
|
323
|
+
<Svg
|
|
324
|
+
width={swatchSize}
|
|
325
|
+
height={swatchSize}
|
|
326
|
+
style={{ position: "absolute" }}
|
|
327
|
+
>
|
|
328
|
+
<Defs>
|
|
329
|
+
<Pattern
|
|
330
|
+
id="swatchChecker"
|
|
331
|
+
x="0"
|
|
332
|
+
y="0"
|
|
333
|
+
width={checkerSize * 2}
|
|
334
|
+
height={checkerSize * 2}
|
|
335
|
+
patternUnits="userSpaceOnUse"
|
|
336
|
+
>
|
|
337
|
+
<Rect
|
|
338
|
+
x="0"
|
|
339
|
+
y="0"
|
|
340
|
+
width={checkerSize}
|
|
341
|
+
height={checkerSize}
|
|
342
|
+
fill="#CCCCCC"
|
|
343
|
+
/>
|
|
344
|
+
<Rect
|
|
345
|
+
x={checkerSize}
|
|
346
|
+
y="0"
|
|
347
|
+
width={checkerSize}
|
|
348
|
+
height={checkerSize}
|
|
349
|
+
fill="#FFFFFF"
|
|
350
|
+
/>
|
|
351
|
+
<Rect
|
|
352
|
+
x="0"
|
|
353
|
+
y={checkerSize}
|
|
354
|
+
width={checkerSize}
|
|
355
|
+
height={checkerSize}
|
|
356
|
+
fill="#FFFFFF"
|
|
357
|
+
/>
|
|
358
|
+
<Rect
|
|
359
|
+
x={checkerSize}
|
|
360
|
+
y={checkerSize}
|
|
361
|
+
width={checkerSize}
|
|
362
|
+
height={checkerSize}
|
|
363
|
+
fill="#CCCCCC"
|
|
364
|
+
/>
|
|
365
|
+
</Pattern>
|
|
366
|
+
</Defs>
|
|
367
|
+
<Rect
|
|
368
|
+
x="0"
|
|
369
|
+
y="0"
|
|
370
|
+
width={swatchSize}
|
|
371
|
+
height={swatchSize}
|
|
372
|
+
fill="url(#swatchChecker)"
|
|
373
|
+
/>
|
|
374
|
+
</Svg>
|
|
375
|
+
)}
|
|
376
|
+
<View
|
|
377
|
+
style={{
|
|
378
|
+
position: "absolute",
|
|
379
|
+
width: "100%",
|
|
380
|
+
height: "100%",
|
|
381
|
+
backgroundColor: swatchColor,
|
|
382
|
+
}}
|
|
383
|
+
/>
|
|
384
|
+
</Pressable>
|
|
254
385
|
|
|
255
386
|
<Modal
|
|
256
387
|
visible={modalVisible}
|
|
@@ -258,29 +389,34 @@ export const ColorPicker = React.forwardRef<ColorPickerRef, ColorPickerProps>(
|
|
|
258
389
|
animationType="fade"
|
|
259
390
|
onRequestClose={() => setModalVisible(false)}
|
|
260
391
|
>
|
|
261
|
-
<
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
padding: 20,
|
|
270
|
-
},
|
|
271
|
-
modalStyle,
|
|
272
|
-
]}
|
|
392
|
+
<View
|
|
393
|
+
style={{
|
|
394
|
+
flex: 1,
|
|
395
|
+
backgroundColor: t.overlay,
|
|
396
|
+
justifyContent: "center",
|
|
397
|
+
alignItems: "center",
|
|
398
|
+
padding: 20,
|
|
399
|
+
}}
|
|
273
400
|
>
|
|
401
|
+
{/* Overlay pressable - absolute positioned behind content */}
|
|
274
402
|
<Pressable
|
|
275
|
-
onPress={() =>
|
|
276
|
-
style={{
|
|
277
|
-
|
|
403
|
+
onPress={() => setModalVisible(false)}
|
|
404
|
+
style={{
|
|
405
|
+
position: "absolute",
|
|
406
|
+
top: 0,
|
|
407
|
+
left: 0,
|
|
408
|
+
right: 0,
|
|
409
|
+
bottom: 0,
|
|
410
|
+
}}
|
|
411
|
+
/>
|
|
412
|
+
{/* Content - not wrapped in Pressable */}
|
|
413
|
+
<View style={[{ width: "100%", alignItems: "center" }, contentStyle]}>
|
|
278
414
|
<PickerPanel
|
|
279
415
|
{...panelProps}
|
|
280
416
|
style={[{ width: panelWidth }, style]}
|
|
281
417
|
/>
|
|
282
|
-
</
|
|
283
|
-
</
|
|
418
|
+
</View>
|
|
419
|
+
</View>
|
|
284
420
|
</Modal>
|
|
285
421
|
</>
|
|
286
422
|
);
|