@darthrapid/react-native-color-picker 0.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.
Files changed (65) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +160 -0
  3. package/lib/module/components/color-picker.js +214 -0
  4. package/lib/module/components/color-picker.js.map +1 -0
  5. package/lib/module/components/hue-strip.js +113 -0
  6. package/lib/module/components/hue-strip.js.map +1 -0
  7. package/lib/module/components/picker-panel.js +121 -0
  8. package/lib/module/components/picker-panel.js.map +1 -0
  9. package/lib/module/components/picker-tab.js +38 -0
  10. package/lib/module/components/picker-tab.js.map +1 -0
  11. package/lib/module/components/recent-tab.js +89 -0
  12. package/lib/module/components/recent-tab.js.map +1 -0
  13. package/lib/module/components/sat-bright-pad.js +171 -0
  14. package/lib/module/components/sat-bright-pad.js.map +1 -0
  15. package/lib/module/components/tab-bar.js +41 -0
  16. package/lib/module/components/tab-bar.js.map +1 -0
  17. package/lib/module/components/values-tab.js +161 -0
  18. package/lib/module/components/values-tab.js.map +1 -0
  19. package/lib/module/index.js +4 -0
  20. package/lib/module/index.js.map +1 -0
  21. package/lib/module/package.json +1 -0
  22. package/lib/module/shared/const.js +43 -0
  23. package/lib/module/shared/const.js.map +1 -0
  24. package/lib/module/types/misc.js +4 -0
  25. package/lib/module/types/misc.js.map +1 -0
  26. package/lib/module/utils/colors.js +52 -0
  27. package/lib/module/utils/colors.js.map +1 -0
  28. package/lib/typescript/package.json +1 -0
  29. package/lib/typescript/src/components/color-picker.d.ts +37 -0
  30. package/lib/typescript/src/components/color-picker.d.ts.map +1 -0
  31. package/lib/typescript/src/components/hue-strip.d.ts +10 -0
  32. package/lib/typescript/src/components/hue-strip.d.ts.map +1 -0
  33. package/lib/typescript/src/components/picker-panel.d.ts +32 -0
  34. package/lib/typescript/src/components/picker-panel.d.ts.map +1 -0
  35. package/lib/typescript/src/components/picker-tab.d.ts +17 -0
  36. package/lib/typescript/src/components/picker-tab.d.ts.map +1 -0
  37. package/lib/typescript/src/components/recent-tab.d.ts +12 -0
  38. package/lib/typescript/src/components/recent-tab.d.ts.map +1 -0
  39. package/lib/typescript/src/components/sat-bright-pad.d.ts +14 -0
  40. package/lib/typescript/src/components/sat-bright-pad.d.ts.map +1 -0
  41. package/lib/typescript/src/components/tab-bar.d.ts +11 -0
  42. package/lib/typescript/src/components/tab-bar.d.ts.map +1 -0
  43. package/lib/typescript/src/components/values-tab.d.ts +17 -0
  44. package/lib/typescript/src/components/values-tab.d.ts.map +1 -0
  45. package/lib/typescript/src/index.d.ts +3 -0
  46. package/lib/typescript/src/index.d.ts.map +1 -0
  47. package/lib/typescript/src/shared/const.d.ts +33 -0
  48. package/lib/typescript/src/shared/const.d.ts.map +1 -0
  49. package/lib/typescript/src/types/misc.d.ts +21 -0
  50. package/lib/typescript/src/types/misc.d.ts.map +1 -0
  51. package/lib/typescript/src/utils/colors.d.ts +14 -0
  52. package/lib/typescript/src/utils/colors.d.ts.map +1 -0
  53. package/package.json +123 -0
  54. package/src/components/color-picker.tsx +290 -0
  55. package/src/components/hue-strip.tsx +134 -0
  56. package/src/components/picker-panel.tsx +176 -0
  57. package/src/components/picker-tab.tsx +48 -0
  58. package/src/components/recent-tab.tsx +90 -0
  59. package/src/components/sat-bright-pad.tsx +158 -0
  60. package/src/components/tab-bar.tsx +59 -0
  61. package/src/components/values-tab.tsx +166 -0
  62. package/src/index.tsx +2 -0
  63. package/src/shared/const.ts +43 -0
  64. package/src/types/misc.ts +24 -0
  65. package/src/utils/colors.ts +57 -0
@@ -0,0 +1,290 @@
1
+ import React, {
2
+ useCallback,
3
+ useImperativeHandle,
4
+ useRef,
5
+ useState,
6
+ } from "react";
7
+ import {
8
+ Modal,
9
+ Pressable,
10
+ type DimensionValue,
11
+ type StyleProp,
12
+ type ViewStyle
13
+ } from "react-native";
14
+ import { PickerPanel } from "./picker-panel";
15
+ import type { ColorPickerLabels, ColorPickerRef, TabId } from "../types/misc";
16
+ import { DEFAULT_LABELS, themes } from "../shared/const";
17
+ import { getContrastColor, hexToHsb, hsbToHex, isValidHex } from "../utils/colors";
18
+
19
+ export type ColorPickerProps = {
20
+ /** Current color value (hex string) */
21
+ value?: string;
22
+ /** Called when color changes */
23
+ onChange?: (hex: string) => void;
24
+ /** Tabs to show (default: ["picker", "values", "recent"]) */
25
+ tabs?: TabId[];
26
+ /** Max recent colors (default: 16) */
27
+ maxRecentColors?: number;
28
+ /** Panel width in modal mode (default: 320). Accepts number or "100%". Ignored in inline mode. */
29
+ panelWidth?: DimensionValue;
30
+ /** Hue strip height (default: 28) */
31
+ hueStripHeight?: number;
32
+ /** Theme (default: "dark") */
33
+ theme?: "light" | "dark";
34
+ /** Disable touch input */
35
+ disabled?: boolean;
36
+ /** Style for the picker panel */
37
+ style?: StyleProp<ViewStyle>;
38
+ /** Swatch size (default: 48) */
39
+ swatchSize?: number;
40
+ /** Swatch border radius (default: 12) */
41
+ swatchBorderRadius?: number;
42
+ /** Style for the swatch trigger */
43
+ swatchStyle?: StyleProp<ViewStyle>;
44
+ /** Render inline instead of modal (default: false) */
45
+ inline?: boolean;
46
+ /** Override default labels for i18n */
47
+ labels?: ColorPickerLabels;
48
+ /** Style for modal wrapper */
49
+ modalStyle?: StyleProp<ViewStyle>;
50
+ };
51
+
52
+ export const ColorPicker = React.forwardRef<ColorPickerRef, ColorPickerProps>(
53
+ (
54
+ {
55
+ value = "#007AFF",
56
+ onChange,
57
+ tabs = ["picker", "values", "recent"],
58
+ maxRecentColors = 16,
59
+ panelWidth = "100%",
60
+ hueStripHeight = 28,
61
+ theme: themeName = "dark",
62
+ disabled = false,
63
+ style,
64
+ swatchSize = 48,
65
+ swatchBorderRadius = 12,
66
+ swatchStyle,
67
+ inline = false,
68
+ labels: userLabels,
69
+ modalStyle,
70
+ },
71
+ ref,
72
+ ) => {
73
+ const t = themes[themeName];
74
+ const labels = { ...DEFAULT_LABELS, ...userLabels };
75
+
76
+ const initial = hexToHsb(value);
77
+ const [hue, setHue] = useState(initial.h);
78
+ const [sat, setSat] = useState(initial.s);
79
+ const [bright, setBright] = useState(initial.b);
80
+ const [hexInput, setHexInput] = useState(value.toUpperCase());
81
+ const [recentColors, setRecentColors] = useState<string[]>([]);
82
+ const [modalVisible, setModalVisible] = useState(false);
83
+
84
+ const currentHex = hsbToHex(hue, sat, bright);
85
+ const contrastColor = getContrastColor(currentHex);
86
+
87
+ // Refs for stable handler closures
88
+ const hueRef = useRef(hue);
89
+ const satRef = useRef(sat);
90
+ const brightRef = useRef(bright);
91
+ hueRef.current = hue;
92
+ satRef.current = sat;
93
+ brightRef.current = bright;
94
+
95
+ // Sync hex input display — only update when NOT focused
96
+ const hexInputFocusedRef = useRef(false);
97
+ const currentHexRef = useRef(currentHex);
98
+ currentHexRef.current = currentHex;
99
+
100
+ if (!hexInputFocusedRef.current && hexInput !== currentHex.toUpperCase()) {
101
+ setHexInput(currentHex.toUpperCase());
102
+ }
103
+
104
+ // Sync from external value prop — only when parent actually changes value
105
+ const lastExternalValue = useRef(value);
106
+ if (value !== lastExternalValue.current) {
107
+ lastExternalValue.current = value;
108
+ if (isValidHex(value)) {
109
+ const hsb = hexToHsb(value);
110
+ // Only update if actually different to avoid loops
111
+ if (hsb.h !== hue || hsb.s !== sat || hsb.b !== bright) {
112
+ setHue(hsb.h);
113
+ setSat(hsb.s);
114
+ setBright(hsb.b);
115
+ }
116
+ }
117
+ }
118
+
119
+ const notifyChange = useCallback(
120
+ (h: number, s: number, b: number) => {
121
+ const hex = hsbToHex(h, s, b);
122
+ lastExternalValue.current = hex; // prevent sync-back
123
+ onChange?.(hex);
124
+ },
125
+ [onChange],
126
+ );
127
+
128
+ const addToRecent = useCallback(
129
+ (hex: string) => {
130
+ setRecentColors((prev) => {
131
+ const filtered = prev.filter((c) => c !== hex);
132
+ return [hex, ...filtered].slice(0, maxRecentColors);
133
+ });
134
+ },
135
+ [maxRecentColors],
136
+ );
137
+
138
+ const handleHueChange = useCallback(
139
+ (h: number) => {
140
+ setHue(h);
141
+ notifyChange(h, satRef.current, brightRef.current);
142
+ },
143
+ [notifyChange],
144
+ );
145
+
146
+ const handleSatBrightChange = useCallback(
147
+ ({ s, b }: { s: number; b: number }) => {
148
+ setSat(s);
149
+ setBright(b);
150
+ notifyChange(hueRef.current, s, b);
151
+ },
152
+ [notifyChange],
153
+ );
154
+
155
+ const handleHexSubmit = useCallback(() => {
156
+ const clean = hexInput.startsWith("#") ? hexInput : `#${hexInput}`;
157
+ if (isValidHex(clean)) {
158
+ const hsb = hexToHsb(clean);
159
+ setHue(hsb.h);
160
+ setSat(hsb.s);
161
+ setBright(hsb.b);
162
+ notifyChange(hsb.h, hsb.s, hsb.b);
163
+ addToRecent(clean);
164
+ } else {
165
+ setHexInput(currentHex.toUpperCase());
166
+ }
167
+ }, [hexInput, currentHex, notifyChange, addToRecent]);
168
+
169
+ const handleRecentSelect = useCallback(
170
+ (hex: string) => {
171
+ const hsb = hexToHsb(hex);
172
+ setHue(hsb.h);
173
+ setSat(hsb.s);
174
+ setBright(hsb.b);
175
+ notifyChange(hsb.h, hsb.s, hsb.b);
176
+ },
177
+ [notifyChange],
178
+ );
179
+
180
+ useImperativeHandle(
181
+ ref,
182
+ () => ({
183
+ getColor: () => currentHex,
184
+ setColor: (hex: string) => {
185
+ if (!isValidHex(hex)) return;
186
+ const hsb = hexToHsb(hex);
187
+ setHue(hsb.h);
188
+ setSat(hsb.s);
189
+ setBright(hsb.b);
190
+ lastExternalValue.current = hex;
191
+ notifyChange(hsb.h, hsb.s, hsb.b);
192
+ },
193
+ clearRecent: () => setRecentColors([]),
194
+ open: () => setModalVisible(true),
195
+ close: () => setModalVisible(false),
196
+ }),
197
+ [currentHex, notifyChange],
198
+ );
199
+
200
+ const panelProps = {
201
+ hue,
202
+ sat,
203
+ bright,
204
+ currentHex,
205
+ contrastColor,
206
+ hexInput,
207
+ recentColors,
208
+ tabs,
209
+ hueStripHeight,
210
+ disabled,
211
+ t,
212
+ labels,
213
+ onHueChange: handleHueChange,
214
+ onSatBrightChange: handleSatBrightChange,
215
+ onHexInputChange: setHexInput,
216
+ onHexSubmit: handleHexSubmit,
217
+ onHexInputFocus: () => {
218
+ hexInputFocusedRef.current = true;
219
+ },
220
+ onHexInputBlur: () => {
221
+ hexInputFocusedRef.current = false;
222
+ },
223
+ onSaveRecent: () => addToRecent(currentHex),
224
+ onRecentSelect: handleRecentSelect,
225
+ onClearRecent: () => setRecentColors([]),
226
+ };
227
+
228
+ if (inline) {
229
+ return <PickerPanel {...panelProps} style={style} />;
230
+ }
231
+
232
+ return (
233
+ <>
234
+ <Pressable
235
+ onPress={() => setModalVisible(true)}
236
+ disabled={disabled}
237
+ style={[
238
+ {
239
+ width: swatchSize,
240
+ height: swatchSize,
241
+ borderRadius: swatchBorderRadius,
242
+ backgroundColor: currentHex,
243
+ borderWidth: 2,
244
+ borderColor: t.border,
245
+ shadowColor: "#000",
246
+ shadowOffset: { width: 0, height: 2 },
247
+ shadowOpacity: 0.15,
248
+ shadowRadius: 4,
249
+ elevation: 3,
250
+ },
251
+ swatchStyle,
252
+ ]}
253
+ />
254
+
255
+ <Modal
256
+ visible={modalVisible}
257
+ transparent
258
+ animationType="fade"
259
+ onRequestClose={() => setModalVisible(false)}
260
+ >
261
+ <Pressable
262
+ onPress={() => setModalVisible(false)}
263
+ style={[
264
+ {
265
+ flex: 1,
266
+ backgroundColor: t.overlay,
267
+ justifyContent: "center",
268
+ alignItems: "center",
269
+ padding: 20,
270
+ },
271
+ modalStyle,
272
+ ]}
273
+ >
274
+ <Pressable
275
+ onPress={() => {}}
276
+ style={{ width: "100%", alignItems: "center" }}
277
+ >
278
+ <PickerPanel
279
+ {...panelProps}
280
+ style={[{ width: panelWidth }, style]}
281
+ />
282
+ </Pressable>
283
+ </Pressable>
284
+ </Modal>
285
+ </>
286
+ );
287
+ },
288
+ );
289
+
290
+ ColorPicker.displayName = "ColorPicker";
@@ -0,0 +1,134 @@
1
+ import React, {
2
+ useCallback,
3
+ useRef,
4
+ useState
5
+ } from "react";
6
+ import {
7
+ PanResponder,
8
+ View
9
+ } from "react-native";
10
+ import Svg, { Defs, LinearGradient, Rect, Stop } from "react-native-svg";
11
+ import { hsbToHex } from "../utils/colors";
12
+
13
+ type HueStripProps = {
14
+ hue: number;
15
+ height: number;
16
+ disabled: boolean;
17
+ thumbBorder: string;
18
+ onChange: (hue: number) => void;
19
+ }
20
+
21
+ export function HueStrip({
22
+ hue,
23
+ height,
24
+ disabled,
25
+ thumbBorder,
26
+ onChange,
27
+ }: HueStripProps) {
28
+ const containerRef = useRef<React.ComponentRef<typeof View>>(null);
29
+ const originXRef = useRef(0);
30
+ const widthRef = useRef(0);
31
+ const disabledRef = useRef(disabled);
32
+ const onChangeRef = useRef(onChange);
33
+ disabledRef.current = disabled;
34
+ onChangeRef.current = onChange;
35
+
36
+ const [layoutWidth, setLayoutWidth] = useState(0);
37
+
38
+ const calcHue = useCallback((pageX: number) => {
39
+ const w = widthRef.current;
40
+ if (w === 0) return 0;
41
+ const x = Math.max(0, Math.min(pageX - originXRef.current, w));
42
+ return Math.round((x / w) * 360);
43
+ }, []);
44
+
45
+ const panResponder = useRef(
46
+ PanResponder.create({
47
+ onStartShouldSetPanResponder: () => !disabledRef.current,
48
+ onStartShouldSetPanResponderCapture: () => !disabledRef.current,
49
+ onMoveShouldSetPanResponder: () => !disabledRef.current,
50
+ onMoveShouldSetPanResponderCapture: () => !disabledRef.current,
51
+ onPanResponderTerminationRequest: () => false,
52
+ onPanResponderGrant: (evt) => {
53
+ const { pageX } = evt.nativeEvent;
54
+ containerRef.current?.measure((_x: number, _y: number, w: number, _h: number, ox: number) => {
55
+ originXRef.current = ox;
56
+ widthRef.current = w;
57
+ onChangeRef.current(calcHue(pageX));
58
+ });
59
+ },
60
+ onPanResponderMove: (evt) => {
61
+ onChangeRef.current(calcHue(evt.nativeEvent.pageX));
62
+ },
63
+ }),
64
+ ).current;
65
+
66
+ const hueColors = [
67
+ "#FF0000",
68
+ "#FFFF00",
69
+ "#00FF00",
70
+ "#00FFFF",
71
+ "#0000FF",
72
+ "#FF00FF",
73
+ "#FF0000",
74
+ ];
75
+ const thumbLeft = layoutWidth > 0 ? (hue / 360) * layoutWidth : 0;
76
+ const thumbSize = height + 6;
77
+
78
+ return (
79
+ <View
80
+ ref={containerRef}
81
+ onLayout={(e) => {
82
+ const w = e.nativeEvent.layout.width;
83
+ setLayoutWidth(w);
84
+ widthRef.current = w;
85
+ }}
86
+ style={{ width: "100%", height, overflow: "visible" }}
87
+ {...panResponder.panHandlers}
88
+ >
89
+ {layoutWidth > 0 && (
90
+ <>
91
+ <Svg width={layoutWidth} height={height}>
92
+ <Defs>
93
+ <LinearGradient id="hueGrad" x1="0" y1="0" x2="1" y2="0">
94
+ {hueColors.map((color, i) => (
95
+ <Stop
96
+ key={i}
97
+ offset={`${(i / (hueColors.length - 1)) * 100}%`}
98
+ stopColor={color}
99
+ />
100
+ ))}
101
+ </LinearGradient>
102
+ </Defs>
103
+ <Rect
104
+ x="0"
105
+ y="0"
106
+ width={layoutWidth}
107
+ height={height}
108
+ rx={height / 2}
109
+ fill="url(#hueGrad)"
110
+ />
111
+ </Svg>
112
+ <View
113
+ style={{
114
+ position: "absolute",
115
+ left: thumbLeft - thumbSize / 2,
116
+ top: -3,
117
+ width: thumbSize,
118
+ height: thumbSize,
119
+ borderRadius: thumbSize / 2,
120
+ borderWidth: 3,
121
+ borderColor: thumbBorder,
122
+ backgroundColor: hsbToHex(hue, 100, 100),
123
+ shadowColor: "#000",
124
+ shadowOffset: { width: 0, height: 2 },
125
+ shadowOpacity: 0.3,
126
+ shadowRadius: 4,
127
+ elevation: 5,
128
+ }}
129
+ />
130
+ </>
131
+ )}
132
+ </View>
133
+ );
134
+ }
@@ -0,0 +1,176 @@
1
+ import { useState } from "react";
2
+ import {
3
+ Pressable,
4
+ Text,
5
+ View,
6
+ type StyleProp,
7
+ type ViewStyle
8
+ } from "react-native";
9
+ import type { ColorPickerLabels, TabId, Theme } from "../types/misc";
10
+ import { PickerTab } from "./picker-tab";
11
+ import { RecentTab } from "./recent-tab";
12
+ import { TabBar } from "./tab-bar";
13
+ import { ValuesTab } from "./values-tab";
14
+
15
+ type PickerPanelProps = {
16
+ hue: number;
17
+ sat: number;
18
+ bright: number;
19
+ currentHex: string;
20
+ contrastColor: string;
21
+ hexInput: string;
22
+ recentColors: string[];
23
+ tabs: TabId[];
24
+ hueStripHeight: number;
25
+ disabled: boolean;
26
+ t: Theme;
27
+ labels: Required<ColorPickerLabels>;
28
+ onHueChange: (h: number) => void;
29
+ onSatBrightChange: (vals: { s: number; b: number }) => void;
30
+ onHexInputChange: (text: string) => void;
31
+ onHexSubmit: () => void;
32
+ onHexInputFocus: () => void;
33
+ onHexInputBlur: () => void;
34
+ onSaveRecent: () => void;
35
+ onRecentSelect: (hex: string) => void;
36
+ onClearRecent: () => void;
37
+ style?: StyleProp<ViewStyle>;
38
+ }
39
+
40
+ export function PickerPanel({
41
+ hue,
42
+ sat,
43
+ bright,
44
+ currentHex,
45
+ contrastColor,
46
+ hexInput,
47
+ recentColors,
48
+ tabs,
49
+ hueStripHeight,
50
+ disabled,
51
+ t,
52
+ onHueChange,
53
+ onSatBrightChange,
54
+ onHexInputChange,
55
+ onHexSubmit,
56
+ onHexInputFocus,
57
+ onHexInputBlur,
58
+ onSaveRecent,
59
+ onRecentSelect,
60
+ onClearRecent,
61
+ labels,
62
+ style,
63
+ }: PickerPanelProps) {
64
+ const [activeTab, setActiveTab] = useState<TabId>(tabs[0]!);
65
+
66
+ return (
67
+ <View
68
+ style={[
69
+ { backgroundColor: t.background, borderRadius: 20, overflow: "hidden" },
70
+ style,
71
+ ]}
72
+ >
73
+ {/* Color preview header */}
74
+ <View
75
+ style={{
76
+ height: 64,
77
+ backgroundColor: currentHex,
78
+ justifyContent: "flex-end",
79
+ paddingHorizontal: 16,
80
+ paddingBottom: 12,
81
+ }}
82
+ >
83
+ <View
84
+ style={{
85
+ flexDirection: "row",
86
+ alignItems: "center",
87
+ justifyContent: "space-between",
88
+ }}
89
+ >
90
+ <Text
91
+ style={{
92
+ fontSize: 18,
93
+ fontWeight: "700",
94
+ color: contrastColor,
95
+ fontVariant: ["tabular-nums"],
96
+ letterSpacing: 0.5,
97
+ }}
98
+ >
99
+ {currentHex.toUpperCase()}
100
+ </Text>
101
+ <Pressable
102
+ onPress={onSaveRecent}
103
+ style={({ pressed }) => ({
104
+ backgroundColor: pressed
105
+ ? contrastColor === "#FFFFFF"
106
+ ? "rgba(255,255,255,0.5)"
107
+ : "rgba(0,0,0,0.3)"
108
+ : contrastColor === "#FFFFFF"
109
+ ? "rgba(255,255,255,0.2)"
110
+ : "rgba(0,0,0,0.1)",
111
+ paddingHorizontal: 12,
112
+ paddingVertical: 5,
113
+ borderRadius: 8,
114
+ })}
115
+ >
116
+ <Text
117
+ style={{ color: contrastColor, fontSize: 12, fontWeight: "600" }}
118
+ >
119
+ {labels.save}
120
+ </Text>
121
+ </Pressable>
122
+ </View>
123
+ </View>
124
+
125
+ {/* Tab bar */}
126
+ {tabs.length > 1 && (
127
+ <TabBar
128
+ tabs={tabs}
129
+ active={activeTab}
130
+ onSelect={setActiveTab}
131
+ t={t}
132
+ labels={labels}
133
+ />
134
+ )}
135
+
136
+ {/* Tab content */}
137
+ {activeTab === "picker" && (
138
+ <PickerTab
139
+ hue={hue}
140
+ sat={sat}
141
+ bright={bright}
142
+ hueStripHeight={hueStripHeight}
143
+ disabled={disabled}
144
+ t={t}
145
+ onHueChange={onHueChange}
146
+ onSatBrightChange={onSatBrightChange}
147
+ />
148
+ )}
149
+ {activeTab === "values" && (
150
+ <ValuesTab
151
+ hue={hue}
152
+ sat={sat}
153
+ bright={bright}
154
+ currentHex={currentHex}
155
+ hexInput={hexInput}
156
+ disabled={disabled}
157
+ t={t}
158
+ onHexInputChange={onHexInputChange}
159
+ onHexSubmit={onHexSubmit}
160
+ onHexInputFocus={onHexInputFocus}
161
+ onHexInputBlur={onHexInputBlur}
162
+ />
163
+ )}
164
+ {activeTab === "recent" && (
165
+ <RecentTab
166
+ recentColors={recentColors}
167
+ disabled={disabled}
168
+ t={t}
169
+ labels={labels}
170
+ onSelect={onRecentSelect}
171
+ onClear={onClearRecent}
172
+ />
173
+ )}
174
+ </View>
175
+ );
176
+ }
@@ -0,0 +1,48 @@
1
+ import {
2
+ View
3
+ } from "react-native";
4
+ import type { Theme } from "../types/misc";
5
+ import { HueStrip } from "./hue-strip";
6
+ import { SatBrightPad } from "./sat-bright-pad";
7
+
8
+ type PickerTabProps = {
9
+ hue: number;
10
+ sat: number;
11
+ bright: number;
12
+ hueStripHeight: number;
13
+ disabled: boolean;
14
+ t: Theme;
15
+ onHueChange: (h: number) => void;
16
+ onSatBrightChange: (vals: { s: number; b: number }) => void;
17
+ }
18
+
19
+ export function PickerTab({
20
+ hue,
21
+ sat,
22
+ bright,
23
+ hueStripHeight,
24
+ disabled,
25
+ t,
26
+ onHueChange,
27
+ onSatBrightChange,
28
+ }: PickerTabProps) {
29
+ return (
30
+ <View style={{ gap: 16, padding: 20 }}>
31
+ <SatBrightPad
32
+ hue={hue}
33
+ sat={sat}
34
+ bright={bright}
35
+ disabled={disabled}
36
+ thumbBorder={t.thumbBorder}
37
+ onChange={onSatBrightChange}
38
+ />
39
+ <HueStrip
40
+ hue={hue}
41
+ height={hueStripHeight}
42
+ disabled={disabled}
43
+ thumbBorder={t.thumbBorder}
44
+ onChange={onHueChange}
45
+ />
46
+ </View>
47
+ );
48
+ }