Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 | import React, { useEffect, useMemo, useState, useCallback, useRef } from 'react';
// Debounce utility function
const useDebounce = (callback, delay) => {
const timeoutRef = useRef(null);
useEffect(() => {
// Cleanup on unmount
return () => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
};
}, []);
return useCallback(
(...args) => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
timeoutRef.current = setTimeout(() => {
callback(...args);
}, delay);
},
[callback, delay],
);
};
// Simple hex utilities
const toHashHex = (hex) => {
if (!hex) return null;
const v = hex.trim();
const withHash = v.startsWith('#') ? v : `#${v}`;
return /^#([0-9a-fA-F]{6})$/.test(withHash) ? withHash.toUpperCase() : null;
};
const fromColorInput = (value) => {
// input type=color always yields #rrggbb (lowercase). Normalize to uppercase.
if (typeof value !== 'string') return null;
return /^#[0-9a-f]{6}$/.test(value) ? value.toUpperCase() : toHashHex(value);
};
// SLDS-styled ColorPicker with native input type="color" and a synced hex input
const ColorPicker = ({ id, value, onChange, label = 'Color', className = '' }) => {
const initial = toHashHex(value) || '#5679C0';
const [colorHex, setColorHex] = useState(initial);
const [hexText, setHexText] = useState(initial);
// Sync internal state when parent value changes
useEffect(() => {
const norm = toHashHex(value);
if (norm && norm !== colorHex) {
setColorHex(norm);
setHexText(norm);
}
}, [value]);
const hasError = useMemo(() => !!hexText && !toHashHex(hexText), [hexText]);
// Create debounced version of onChange
const debouncedOnChange = useDebounce((value) => {
onChange && onChange(value);
}, 500);
const handleColorChange = (e) => {
const next = fromColorInput(e.target.value) || '#000000';
setColorHex(next);
setHexText(next);
debouncedOnChange(next);
};
const handleHexInputChange = (e) => {
const nextText = e.target.value;
setHexText(nextText);
const normalized = toHashHex(nextText);
if (normalized) {
setColorHex(normalized);
debouncedOnChange(normalized);
}
};
return (
<div className={`slds-color-picker ${className}`.trim()}>
<div className="slds-grid slds-align_absolute-center">
<div className="slds-col">
<div className="slds-form-element">
<label className="slds-form-element__label" htmlFor={id ? `${id}-color` : 'color-input'}>
{label}
</label>
<div className="slds-grid slds-gutters_x-small slds-align_absolute-center">
<div className="slds-form-element__control slds-m-right_x-small">
<input
id={id ? `${id}-color` : 'color-input'}
type="color"
className="slds-p-horizontal_xx-small"
value={colorHex}
onChange={handleColorChange}
aria-label="Choose color"
style={{
width: 'var(--slds-g-sizing-10)',
minWidth: 'var(--slds-g-sizing-10)',
height: 'var(--slds-g-sizing-9)',
borderRadius: 'var(--slds-g-radius-border-1)',
}}
/>
</div>
<div className="slds-form-element__control">
<input
id={id ? `${id}-hex` : 'hex-input'}
type="text"
className="slds-input"
value={hexText}
onChange={handleHexInputChange}
placeholder="#RRGGBB"
aria-describedby={hasError ? (id ? `${id}-hex-error` : 'hex-error') : undefined}
/>
</div>
{hasError ? (
<div className="slds-form-element__help" id={id ? `${id}-hex-error` : 'hex-error'}>
Enter a valid hex color like #FFAA00
</div>
) : null}
</div>
</div>
</div>
</div>
</div>
);
};
export default ColorPicker;
|