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 | 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;
|