gdp-color-picker 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/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "gdp-color-picker",
3
+ "version": "1.0.0",
4
+ "author": "Gap-L <13620238719@163.com>",
5
+ "private": false,
6
+ "description": "A custom color picker component compatible with React 16.8",
7
+ "source": "src/lib/index.js",
8
+ "main": "dist/index.js",
9
+ "module": "dist/index.module.js",
10
+ "unpkg": "dist/index.umd.js",
11
+ "files": [
12
+ "dist",
13
+ "src/lib",
14
+ "README.md"
15
+ ],
16
+ "scripts": {
17
+ "start": "react-scripts start",
18
+ "build:demo": "react-scripts build",
19
+ "build": "microbundle --jsx React.createElement --jsxFragment React.Fragment --globals react=React",
20
+ "prepare": "npm run build"
21
+ },
22
+ "peerDependencies": {
23
+ "react": ">=16.8.0",
24
+ "react-dom": ">=16.8.0"
25
+ },
26
+ "dependencies": {
27
+ "prop-types": "^15.7.0"
28
+ },
29
+ "devDependencies": {
30
+ "autoprefixer": "^10.4.23",
31
+ "microbundle": "^0.15.1",
32
+ "postcss": "^8.5.6",
33
+ "react": "^16.8.0",
34
+ "react-dom": "^16.8.0",
35
+ "react-scripts": "3.4.0"
36
+ },
37
+ "engines": {
38
+ "node": ">=10"
39
+ },
40
+ "browserslist": {
41
+ "production": [
42
+ ">0.2%",
43
+ "not dead",
44
+ "not op_mini all"
45
+ ],
46
+ "development": [
47
+ "last 1 chrome version",
48
+ "last 1 firefox version",
49
+ "last 1 safari version"
50
+ ]
51
+ }
52
+ }
@@ -0,0 +1,70 @@
1
+
2
+ import React, { useRef, useEffect, useState, useCallback } from 'react';
3
+ import { clamp } from './utils/color';
4
+
5
+ const ColorBoard = ({ hue, saturation, value, onChange }) => {
6
+ const containerRef = useRef(null);
7
+ const [isDragging, setIsDragging] = useState(false);
8
+
9
+ const handleChange = useCallback((e) => {
10
+ if (!containerRef.current) return;
11
+ const { width, height, left, top } = containerRef.current.getBoundingClientRect();
12
+ const x = clamp(e.clientX - left, 0, width);
13
+ const y = clamp(e.clientY - top, 0, height);
14
+
15
+ const newSaturation = x / width;
16
+ const newValue = 1 - y / height;
17
+
18
+ onChange(newSaturation, newValue);
19
+ }, [onChange]);
20
+
21
+ const handleMouseDown = (e) => {
22
+ setIsDragging(true);
23
+ handleChange(e);
24
+ };
25
+
26
+ const handleMouseUp = useCallback(() => {
27
+ setIsDragging(false);
28
+ }, []);
29
+
30
+ useEffect(() => {
31
+ const handleMouseMove = (e) => {
32
+ if (isDragging) {
33
+ handleChange(e);
34
+ }
35
+ };
36
+
37
+ if (isDragging) {
38
+ window.addEventListener('mousemove', handleMouseMove);
39
+ window.addEventListener('mouseup', handleMouseUp);
40
+ }
41
+
42
+ return () => {
43
+ window.removeEventListener('mousemove', handleMouseMove);
44
+ window.removeEventListener('mouseup', handleMouseUp);
45
+ };
46
+ }, [isDragging, handleChange, handleMouseUp]);
47
+
48
+ return (
49
+ <div
50
+ ref={containerRef}
51
+ className="color-board"
52
+ style={{
53
+ backgroundColor: `hsl(${hue}, 100%, 50%)`,
54
+ }}
55
+ onMouseDown={handleMouseDown}
56
+ >
57
+ <div className="color-board-white" />
58
+ <div className="color-board-black" />
59
+ <div
60
+ className="color-board-handler"
61
+ style={{
62
+ left: `${saturation * 100}%`,
63
+ top: `${(1 - value) * 100}%`,
64
+ }}
65
+ />
66
+ </div>
67
+ );
68
+ };
69
+
70
+ export default ColorBoard;
@@ -0,0 +1,140 @@
1
+ import React, { useState, useEffect, useCallback, useRef } from 'react';
2
+ import { rgbToHex, hexToRgb, hsvToRgb, rgbToHsv, rgbToHsl, hslToRgb } from './utils/color';
3
+
4
+ const ColorInput = ({ hue, saturation, value, alpha, onChange }) => {
5
+ const [mode, setMode] = useState('HEX');
6
+ const [localValue, setLocalValue] = useState({});
7
+ const [showDropdown, setShowDropdown] = useState(false);
8
+ const dropdownRef = useRef(null);
9
+
10
+ // Close dropdown when clicking outside
11
+ useEffect(() => {
12
+ const handleClickOutside = (event) => {
13
+ if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
14
+ setShowDropdown(false);
15
+ }
16
+ };
17
+ document.addEventListener('mousedown', handleClickOutside);
18
+ return () => {
19
+ document.removeEventListener('mousedown', handleClickOutside);
20
+ };
21
+ }, []);
22
+
23
+ const getRgb = useCallback(() => hsvToRgb(hue, saturation, value), [hue, saturation, value]);
24
+
25
+ useEffect(() => {
26
+ const rgb = getRgb();
27
+ if (mode === 'HEX') {
28
+ setLocalValue({ hex: rgbToHex(rgb.r, rgb.g, rgb.b) });
29
+ } else if (mode === 'RGB') {
30
+ setLocalValue({ r: rgb.r, g: rgb.g, b: rgb.b });
31
+ } else if (mode === 'HSL') {
32
+ const hsl = rgbToHsl(rgb.r, rgb.g, rgb.b);
33
+ setLocalValue({ h: Math.round(hsl.h), s: Math.round(hsl.s * 100), l: Math.round(hsl.l * 100) });
34
+ }
35
+ }, [hue, saturation, value, mode, getRgb]);
36
+
37
+ const handleHexChange = (e) => {
38
+ const val = e.target.value;
39
+ setLocalValue({ ...localValue, hex: val });
40
+ if (/^#?([0-9A-F]{3}|[0-9A-F]{6})$/i.test(val)) {
41
+ const rgb = hexToRgb(val);
42
+ if (rgb) {
43
+ const hsv = rgbToHsv(rgb.r, rgb.g, rgb.b);
44
+ onChange({ ...hsv, a: alpha });
45
+ }
46
+ }
47
+ };
48
+
49
+ const handleRgbChange = (key, val) => {
50
+ const newValue = { ...localValue, [key]: val };
51
+ setLocalValue(newValue);
52
+ const r = parseInt(newValue.r, 10);
53
+ const g = parseInt(newValue.g, 10);
54
+ const b = parseInt(newValue.b, 10);
55
+ if (!isNaN(r) && !isNaN(g) && !isNaN(b)) {
56
+ const hsv = rgbToHsv(Math.min(255, Math.max(0, r)), Math.min(255, Math.max(0, g)), Math.min(255, Math.max(0, b)));
57
+ onChange({ ...hsv, a: alpha });
58
+ }
59
+ };
60
+
61
+ const handleHslChange = (key, val) => {
62
+ const newValue = { ...localValue, [key]: val };
63
+ setLocalValue(newValue);
64
+ const h = parseInt(newValue.h, 10);
65
+ const s = parseInt(newValue.s, 10);
66
+ const l = parseInt(newValue.l, 10);
67
+ if (!isNaN(h) && !isNaN(s) && !isNaN(l)) {
68
+ const rgb = hslToRgb(Math.min(360, Math.max(0, h)), Math.min(100, Math.max(0, s)) / 100, Math.min(100, Math.max(0, l)) / 100);
69
+ const hsv = rgbToHsv(rgb.r, rgb.g, rgb.b);
70
+ onChange({ ...hsv, a: alpha });
71
+ }
72
+ };
73
+
74
+ const openEyeDropper = async () => {
75
+ if (!window.EyeDropper) return;
76
+ const eyeDropper = new window.EyeDropper();
77
+ try {
78
+ const result = await eyeDropper.open();
79
+ const rgb = hexToRgb(result.sRGBHex);
80
+ if (rgb) {
81
+ const hsv = rgbToHsv(rgb.r, rgb.g, rgb.b);
82
+ onChange({ ...hsv, a: alpha });
83
+ }
84
+ } catch (e) {
85
+ // User canceled
86
+ }
87
+ };
88
+
89
+ return (
90
+ <div className="color-input-container">
91
+ <div className="color-input-row">
92
+ <div className="mode-selector" ref={dropdownRef} onClick={() => setShowDropdown(!showDropdown)}>
93
+ <span>{mode}</span>
94
+ <span className="arrow" style={{ fontSize: '10px', marginLeft: '4px', transform: showDropdown ? 'rotate(180deg)' : 'rotate(0deg)', display: 'inline-block', transition: 'transform 0.2s' }}>▼</span>
95
+ {showDropdown && (
96
+ <div className="mode-dropdown">
97
+ <div className="mode-option" onClick={(e) => { e.stopPropagation(); setMode('HEX'); setShowDropdown(false); }}>HEX</div>
98
+ <div className="mode-option" onClick={(e) => { e.stopPropagation(); setMode('RGB'); setShowDropdown(false); }}>RGB</div>
99
+ <div className="mode-option" onClick={(e) => { e.stopPropagation(); setMode('HSL'); setShowDropdown(false); }}>HSL</div>
100
+ </div>
101
+ )}
102
+ </div>
103
+
104
+ {window.EyeDropper && (
105
+ <div className="eyedropper-icon" onClick={openEyeDropper} title="Pick Color">
106
+ <svg viewBox="0 0 24 24" width="16" height="16">
107
+ <path fill="currentColor" d="M17.5,1.5c-0.8,0-1.5,0.3-2.1,0.9l-8,8c-0.6,0.6-0.9,1.3-0.9,2.1v2.5l-4,4l1.5,1.5l4-4h2.5c0.8,0,1.5-0.3,2.1-0.9l8-8c0.6-0.6,0.9-1.3,0.9-2.1s-0.3-1.5-0.9-2.1S18.3,1.5,17.5,1.5z M17.5,10l-8-8l8,8z M6.5,12.5v2.5h-2.5L2,17l2,2l2-2v-2.5h2.5l8-8L10,5L6.5,8.5V12.5z"/>
108
+ <path d="M19.3 8.9L15.1 4.7 17.5 2.3c.4-.4 1-.4 1.4 0l2.7 2.7c.4.4.4 1 0 1.4L19.3 8.9zM13.7 6.1L4 15.8V20h4.2l9.7-9.7-4.2-4.2z" fill="currentColor"/>
109
+ </svg>
110
+ </div>
111
+ )}
112
+ </div>
113
+
114
+ <div className="color-input-fields">
115
+ {mode === 'HEX' && (
116
+ <input className="color-input" value={localValue.hex || ''} onChange={handleHexChange} />
117
+ )}
118
+ {mode === 'RGB' && (
119
+ <>
120
+ <input className="color-input" type="number" placeholder="R" value={localValue.r !== undefined ? localValue.r : ''} onChange={(e) => handleRgbChange('r', e.target.value)} />
121
+ <input className="color-input" type="number" placeholder="G" value={localValue.g !== undefined ? localValue.g : ''} onChange={(e) => handleRgbChange('g', e.target.value)} />
122
+ <input className="color-input" type="number" placeholder="B" value={localValue.b !== undefined ? localValue.b : ''} onChange={(e) => handleRgbChange('b', e.target.value)} />
123
+ </>
124
+ )}
125
+ {mode === 'HSL' && (
126
+ <>
127
+ <input className="color-input" type="number" placeholder="H" value={localValue.h !== undefined ? localValue.h : ''} onChange={(e) => handleHslChange('h', e.target.value)} />
128
+ <input className="color-input" type="number" placeholder="S" value={localValue.s !== undefined ? localValue.s : ''} onChange={(e) => handleHslChange('s', e.target.value)} />
129
+ <input className="color-input" type="number" placeholder="L" value={localValue.l !== undefined ? localValue.l : ''} onChange={(e) => handleHslChange('l', e.target.value)} />
130
+ </>
131
+ )}
132
+ <div className="color-input alpha-display">
133
+ {Math.round(alpha * 100)}%
134
+ </div>
135
+ </div>
136
+ </div>
137
+ );
138
+ };
139
+
140
+ export default ColorInput;
@@ -0,0 +1,86 @@
1
+
2
+ import React, { useRef, useState, useEffect, useCallback } from 'react';
3
+ import { clamp } from './utils/color';
4
+
5
+ const Slider = ({ value, max, onChange, className, style, children }) => {
6
+ const containerRef = useRef(null);
7
+ const [isDragging, setIsDragging] = useState(false);
8
+
9
+ const handleChange = useCallback((e) => {
10
+ if (!containerRef.current) return;
11
+ const { width, left } = containerRef.current.getBoundingClientRect();
12
+ const x = clamp(e.clientX - left, 0, width);
13
+ const newValue = (x / width) * max;
14
+ onChange(newValue);
15
+ }, [max, onChange]);
16
+
17
+ const handleMouseDown = (e) => {
18
+ setIsDragging(true);
19
+ handleChange(e);
20
+ };
21
+
22
+ const handleMouseUp = useCallback(() => {
23
+ setIsDragging(false);
24
+ }, []);
25
+
26
+ useEffect(() => {
27
+ const handleMouseMove = (e) => {
28
+ if (isDragging) {
29
+ handleChange(e);
30
+ }
31
+ };
32
+
33
+ if (isDragging) {
34
+ window.addEventListener('mousemove', handleMouseMove);
35
+ window.addEventListener('mouseup', handleMouseUp);
36
+ }
37
+
38
+ return () => {
39
+ window.removeEventListener('mousemove', handleMouseMove);
40
+ window.removeEventListener('mouseup', handleMouseUp);
41
+ };
42
+ }, [isDragging, handleChange, handleMouseUp]);
43
+
44
+ return (
45
+ <div
46
+ ref={containerRef}
47
+ className={`color-slider ${className || ''}`}
48
+ style={style}
49
+ onMouseDown={handleMouseDown}
50
+ >
51
+ <div
52
+ className="color-slider-handler"
53
+ style={{
54
+ left: `${(value / max) * 100}%`,
55
+ }}
56
+ />
57
+ {children}
58
+ </div>
59
+ );
60
+ };
61
+
62
+ export const HueSlider = ({ hue, onChange }) => {
63
+ return (
64
+ <Slider
65
+ value={hue}
66
+ max={360}
67
+ onChange={onChange}
68
+ className="hue-slider"
69
+ />
70
+ );
71
+ };
72
+
73
+ export const AlphaSlider = ({ alpha, color, onChange }) => {
74
+ const { r, g, b } = color;
75
+ return (
76
+ <Slider
77
+ value={alpha}
78
+ max={1}
79
+ onChange={onChange}
80
+ className="alpha-slider-bg"
81
+ style={{
82
+ background: `linear-gradient(to right, rgba(${r},${g},${b},0) 0%, rgba(${r},${g},${b},1) 100%)`
83
+ }}
84
+ />
85
+ );
86
+ };
@@ -0,0 +1,198 @@
1
+
2
+ .color-picker-panel {
3
+ width: 200px;
4
+ padding: 8px;
5
+ background: #fff;
6
+ border-radius: 4px;
7
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
8
+ user-select: none;
9
+ }
10
+
11
+ /* ColorBoard */
12
+ .color-board {
13
+ position: relative;
14
+ width: 100%;
15
+ height: 150px;
16
+ border-radius: 2px;
17
+ cursor: pointer;
18
+ overflow: hidden;
19
+ }
20
+
21
+ .color-board-white {
22
+ position: absolute;
23
+ top: 0;
24
+ left: 0;
25
+ right: 0;
26
+ bottom: 0;
27
+ background: linear-gradient(to right, #fff, rgba(255, 255, 255, 0));
28
+ }
29
+
30
+ .color-board-black {
31
+ position: absolute;
32
+ top: 0;
33
+ left: 0;
34
+ right: 0;
35
+ bottom: 0;
36
+ background: linear-gradient(to top, #000, rgba(0, 0, 0, 0));
37
+ }
38
+
39
+ .color-board-handler {
40
+ position: absolute;
41
+ width: 10px;
42
+ height: 10px;
43
+ border: 2px solid #fff;
44
+ border-radius: 50%;
45
+ transform: translate(-50%, -50%);
46
+ box-shadow: 0 0 2px rgba(0, 0, 0, 0.5);
47
+ pointer-events: none;
48
+ }
49
+
50
+ /* Sliders */
51
+ .color-slider {
52
+ position: relative;
53
+ width: 100%;
54
+ height: 10px;
55
+ margin-top: 8px;
56
+ border-radius: 5px;
57
+ cursor: pointer;
58
+ }
59
+
60
+ .color-slider-handler {
61
+ position: absolute;
62
+ top: 50%;
63
+ width: 12px;
64
+ height: 12px;
65
+ background: #fff;
66
+ border-radius: 50%;
67
+ transform: translate(-50%, -50%);
68
+ box-shadow: 0 0 2px rgba(0, 0, 0, 0.5);
69
+ pointer-events: none;
70
+ border: 1px solid rgba(0,0,0,0.1);
71
+ }
72
+
73
+ .hue-slider {
74
+ background: linear-gradient(to right, red 0%, #ff0 17%, lime 33%, cyan 50%, blue 66%, magenta 83%, red 100%);
75
+ }
76
+
77
+ .alpha-slider-bg {
78
+ background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAIAAADZF8uwAAAAGUlEQVQYV2M4gwH+YwCzekwMjDjZ1IxjJwApDkHN9Fi7xwAAAABJRU5ErkJggg==');
79
+ }
80
+
81
+ /* Inputs */
82
+ .color-input-container {
83
+ margin-top: 8px;
84
+ display: flex;
85
+ flex-direction: column;
86
+ gap: 8px;
87
+ }
88
+
89
+ .color-input-row {
90
+ display: flex;
91
+ justify-content: space-between;
92
+ align-items: center;
93
+ }
94
+
95
+ .mode-selector {
96
+ position: relative;
97
+ cursor: pointer;
98
+ font-size: 12px;
99
+ color: #333;
100
+ padding: 2px 4px;
101
+ border-radius: 2px;
102
+ user-select: none;
103
+ display: flex;
104
+ align-items: center;
105
+ }
106
+
107
+ .mode-selector:hover {
108
+ background-color: #f5f5f5;
109
+ }
110
+
111
+ .mode-dropdown {
112
+ position: absolute;
113
+ top: 100%;
114
+ left: 0;
115
+ background: #fff;
116
+ border: 1px solid #d9d9d9;
117
+ border-radius: 2px;
118
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
119
+ z-index: 10;
120
+ min-width: 60px;
121
+ }
122
+
123
+ .mode-option {
124
+ padding: 4px 8px;
125
+ cursor: pointer;
126
+ }
127
+
128
+ .mode-option:hover {
129
+ background: #e6f7ff;
130
+ }
131
+
132
+ .eyedropper-icon {
133
+ cursor: pointer;
134
+ padding: 2px;
135
+ border-radius: 2px;
136
+ color: #666;
137
+ display: flex;
138
+ align-items: center;
139
+ }
140
+
141
+ .eyedropper-icon:hover {
142
+ background-color: #f5f5f5;
143
+ color: #333;
144
+ }
145
+
146
+ .color-input-fields {
147
+ display: flex;
148
+ gap: 4px;
149
+ }
150
+
151
+ .color-input {
152
+ width: 100%;
153
+ padding: 4px;
154
+ border: 1px solid #d9d9d9;
155
+ border-radius: 2px;
156
+ font-size: 12px;
157
+ box-sizing: border-box;
158
+ }
159
+
160
+ /* Chrome, Safari, Edge, Opera - hide spin buttons */
161
+ .color-input::-webkit-outer-spin-button,
162
+ .color-input::-webkit-inner-spin-button {
163
+ -webkit-appearance: none;
164
+ margin: 0;
165
+ }
166
+
167
+ /* Firefox */
168
+ .color-input[type=number] {
169
+ -moz-appearance: textfield;
170
+ }
171
+
172
+ .alpha-display {
173
+ width: 40px;
174
+ text-align: center;
175
+ background-color: #f5f5f5;
176
+ color: #666;
177
+ border: 1px solid #d9d9d9;
178
+ display: flex;
179
+ align-items: center;
180
+ justify-content: center;
181
+ }
182
+
183
+ /* Trigger */
184
+ .color-picker-trigger {
185
+ display: inline-block;
186
+ padding: 4px;
187
+ border: 1px solid #d9d9d9;
188
+ border-radius: 4px;
189
+ cursor: pointer;
190
+ background: #fff;
191
+ }
192
+
193
+ .color-block {
194
+ width: 20px;
195
+ height: 20px;
196
+ border-radius: 2px;
197
+ border: 1px solid rgba(0, 0, 0, 0.1);
198
+ }
@@ -0,0 +1,113 @@
1
+
2
+ import React, { useState, useRef, useEffect } from 'react';
3
+ import ColorBoard from './ColorBoard';
4
+ import { HueSlider, AlphaSlider } from './Sliders';
5
+ import ColorInput from './ColorInput';
6
+ import { hsvToRgb, rgbToHex, parseColor } from './utils/color';
7
+ import './index.css';
8
+
9
+ const ColorPicker = ({ value, defaultValue, onChange }) => {
10
+ // Initialize state
11
+ const [isOpen, setIsOpen] = useState(false);
12
+
13
+ // Internal state for color components
14
+ const [color, setColor] = useState(() => {
15
+ const initColor = value || defaultValue || '#1677ff';
16
+ return parseColor(initColor);
17
+ });
18
+
19
+ const containerRef = useRef(null);
20
+
21
+ // Sync with prop value if controlled
22
+ useEffect(() => {
23
+ if (value !== undefined) {
24
+ setColor(parseColor(value));
25
+ }
26
+ }, [value]);
27
+
28
+ const handleChange = (newColor) => {
29
+ const nextColor = { ...color, ...newColor };
30
+ setColor(nextColor);
31
+
32
+ if (onChange) {
33
+ const rgb = hsvToRgb(nextColor.h, nextColor.s, nextColor.v);
34
+ const hex = rgbToHex(rgb.r, rgb.g, rgb.b);
35
+ // Return a rich object or just hex string?
36
+ // AntD returns an object, but simple string is often easier.
37
+ // Let's return the hex string and the object as second arg just in case.
38
+ onChange(hex, { ...rgb, a: nextColor.a });
39
+ }
40
+ };
41
+
42
+ // Click outside to close
43
+ useEffect(() => {
44
+ const handleClickOutside = (event) => {
45
+ if (containerRef.current && !containerRef.current.contains(event.target)) {
46
+ setIsOpen(false);
47
+ }
48
+ };
49
+
50
+ if (isOpen) {
51
+ document.addEventListener('mousedown', handleClickOutside);
52
+ }
53
+
54
+ return () => {
55
+ document.removeEventListener('mousedown', handleClickOutside);
56
+ };
57
+ }, [isOpen]);
58
+
59
+ const rgb = hsvToRgb(color.h, color.s, color.v);
60
+ const displayColor = `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${color.a})`;
61
+
62
+ return (
63
+ <div className="color-picker-container" ref={containerRef} style={{ position: 'relative', display: 'inline-block' }}>
64
+ <div
65
+ className="color-picker-trigger"
66
+ onClick={() => setIsOpen(!isOpen)}
67
+ >
68
+ <div
69
+ className="color-block"
70
+ style={{ backgroundColor: displayColor }}
71
+ />
72
+ </div>
73
+
74
+ {isOpen && (
75
+ <div
76
+ className="color-picker-panel"
77
+ style={{
78
+ position: 'absolute',
79
+ top: '100%',
80
+ left: 0,
81
+ zIndex: 1000,
82
+ marginTop: 4
83
+ }}
84
+ >
85
+ <ColorBoard
86
+ hue={color.h}
87
+ saturation={color.s}
88
+ value={color.v}
89
+ onChange={(s, v) => handleChange({ s, v })}
90
+ />
91
+ <HueSlider
92
+ hue={color.h}
93
+ onChange={(h) => handleChange({ h })}
94
+ />
95
+ <AlphaSlider
96
+ alpha={color.a}
97
+ color={rgb}
98
+ onChange={(a) => handleChange({ a })}
99
+ />
100
+ <ColorInput
101
+ hue={color.h}
102
+ saturation={color.s}
103
+ value={color.v}
104
+ alpha={color.a}
105
+ onChange={(newHsv) => handleChange(newHsv)}
106
+ />
107
+ </div>
108
+ )}
109
+ </div>
110
+ );
111
+ };
112
+
113
+ export default ColorPicker;